diff options
13 files changed, 249 insertions, 95 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index d5a28378c993..2df0507a8864 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -96,13 +96,13 @@ public class MediaControlPanel { */ @Inject public MediaControlPanel(Context context, @Background Executor backgroundExecutor, - ActivityStarter activityStarter, MediaHostStatesManager mediaHostStatesManager, + ActivityStarter activityStarter, MediaViewController mediaViewController, SeekBarViewModel seekBarViewModel) { mContext = context; mBackgroundExecutor = backgroundExecutor; mActivityStarter = activityStarter; mSeekBarViewModel = seekBarViewModel; - mMediaViewController = new MediaViewController(context, mediaHostStatesManager); + mMediaViewController = mediaViewController; loadDimens(); } @@ -366,14 +366,6 @@ public class MediaControlPanel { } /** - * Return the token for the current media session - * @return the token - */ - public MediaSession.Token getMediaSessionToken() { - return mToken; - } - - /** * Get the current media controller * @return the controller */ @@ -382,25 +374,6 @@ public class MediaControlPanel { } /** - * Get the name of the package associated with the current media controller - * @return the package name, or null if no controller - */ - public String getMediaPlayerPackage() { - if (mController == null) { - return null; - } - return mController.getPackageName(); - } - - /** - * Check whether this player has an attached media session. - * @return whether there is a controller with a current media session. - */ - public boolean hasMediaSession() { - return mController != null && mController.getPlaybackState() != null; - } - - /** * Check whether the media controlled by this player is currently playing * @return whether it is playing, or false if no controller information */ diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt index 5d28178a3b1b..0b0ffcede3af 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt @@ -25,19 +25,65 @@ import android.media.session.MediaSession data class MediaData( val initialized: Boolean = false, val backgroundColor: Int, + /** + * App name that will be displayed on the player. + */ val app: String?, + /** + * Icon shown on player, close to app name. + */ val appIcon: Drawable?, + /** + * Artist name. + */ val artist: CharSequence?, + /** + * Song name. + */ val song: CharSequence?, + /** + * Album artwork. + */ val artwork: Icon?, + /** + * List of actions that can be performed on the player: prev, next, play, pause, etc. + */ val actions: List<MediaAction>, + /** + * Same as above, but shown on smaller versions of the player, like in QQS or keyguard. + */ val actionsToShowInCompact: List<Int>, + /** + * Package name of the app that's posting the media. + */ val packageName: String, + /** + * Unique media session identifier. + */ val token: MediaSession.Token?, + /** + * Action to perform when the player is tapped. + * This is unrelated to {@link #actions}. + */ val clickIntent: PendingIntent?, + /** + * Where the media is playing: phone, headphones, ear buds, remote session. + */ val device: MediaDeviceData?, + /** + * When active, a player will be displayed on keyguard and quick-quick settings. + * This is unrelated to the stream being playing or not, a player will not be active if + * timed out, or in resumption mode. + */ + var active: Boolean, + /** + * Action that should be performed to restart a non active session. + */ var resumeAction: Runnable?, - val notificationKey: String = "INVALID", + /** + * Notification key for cancelling a media player after a timeout (when not using resumption.) + */ + val notificationKey: String? = null, var hasCheckedForResume: Boolean = false ) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index 5fe39958fd67..88bdaece5971 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -67,7 +67,7 @@ private const val LUMINOSITY_THRESHOLD = 0.05f private const val SATURATION_MULTIPLIER = 0.8f private val LOADING = MediaData(false, 0, null, null, null, null, null, - emptyList(), emptyList(), "INVALID", null, null, null, null) + emptyList(), emptyList(), "INVALID", null, null, null, true, null) fun isMediaNotification(sbn: StatusBarNotification): Boolean { if (!sbn.notification.hasMediaSession()) { @@ -88,12 +88,12 @@ fun isMediaNotification(sbn: StatusBarNotification): Boolean { class MediaDataManager @Inject constructor( private val context: Context, private val mediaControllerFactory: MediaControllerFactory, - private val mediaTimeoutListener: MediaTimeoutListener, private val notificationEntryManager: NotificationEntryManager, - private val mediaResumeListener: MediaResumeListener, @Background private val backgroundExecutor: Executor, @Main private val foregroundExecutor: Executor, - private val broadcastDispatcher: BroadcastDispatcher + broadcastDispatcher: BroadcastDispatcher, + mediaTimeoutListener: MediaTimeoutListener, + mediaResumeListener: MediaResumeListener ) { private val listeners: MutableSet<Listener> = mutableSetOf() @@ -131,7 +131,6 @@ class MediaDataManager @Inject constructor( mediaTimeoutListener.timeoutCallback = { token: String, timedOut: Boolean -> setTimedOut(token, timedOut) } addListener(mediaTimeoutListener) - if (useMediaResumption) { mediaResumeListener.addTrackToResumeCallback = { desc: MediaDescription, resumeAction: Runnable, token: MediaSession.Token, appName: String, @@ -215,7 +214,7 @@ class MediaDataManager @Inject constructor( mediaEntries.put(packageName, resumeData) } backgroundExecutor.execute { - loadMediaDataInBg(desc, action, token, appName, appIntent, packageName) + loadMediaDataInBgForResumption(desc, action, token, appName, appIntent, packageName) } } @@ -255,16 +254,21 @@ class MediaDataManager @Inject constructor( fun removeListener(listener: Listener) = listeners.remove(listener) private fun setTimedOut(token: String, timedOut: Boolean) { - if (!timedOut) { - return - } mediaEntries[token]?.let { - notificationEntryManager.removeNotification(it.notificationKey, null /* ranking */, - UNDEFINED_DISMISS_REASON) + if (Utils.useMediaResumption(context)) { + if (it.active == !timedOut) { + return + } + it.active = !timedOut + onMediaDataLoaded(token, token, it) + } else { + notificationEntryManager.removeNotification(it.notificationKey, null /* ranking */, + UNDEFINED_DISMISS_REASON) + } } } - private fun loadMediaDataInBg( + private fun loadMediaDataInBgForResumption( desc: MediaDescription, resumeAction: Runnable, token: MediaSession.Token, @@ -272,11 +276,6 @@ class MediaDataManager @Inject constructor( appIntent: PendingIntent, packageName: String ) { - if (resumeAction == null) { - Log.e(TAG, "Resume action cannot be null") - return - } - if (TextUtils.isEmpty(desc.title)) { Log.e(TAG, "Description incomplete") return @@ -298,8 +297,9 @@ class MediaDataManager @Inject constructor( val mediaAction = getResumeMediaAction(resumeAction) foregroundExecutor.execute { onMediaDataLoaded(packageName, null, MediaData(true, Color.DKGRAY, appName, - null, desc.subtitle, desc.title, artworkIcon, listOf(mediaAction), listOf(0), - packageName, token, appIntent, null, resumeAction, packageName)) + null, desc.subtitle, desc.title, artworkIcon, listOf(mediaAction), listOf(0), + packageName, token, appIntent, device = null, active = false, + resumeAction = resumeAction)) } } @@ -430,7 +430,8 @@ class MediaDataManager @Inject constructor( foregroundExecutor.execute { onMediaDataLoaded(key, oldKey, MediaData(true, bgColor, app, smallIconDrawable, artist, song, artWorkIcon, actionIcons, actionsToShowCollapsed, sbn.packageName, token, - notif.contentIntent, null, resumeAction, key)) + notif.contentIntent, null, active = true, resumeAction = resumeAction, + notificationKey = key)) } } @@ -528,13 +529,13 @@ class MediaDataManager @Inject constructor( /** * Are there any media notifications active? */ - fun hasActiveMedia() = mediaEntries.any({ isActive(it.value) }) + fun hasActiveMedia() = mediaEntries.any { it.value.active } - fun isActive(data: MediaData): Boolean { - if (data.token == null) { + fun isActive(token: MediaSession.Token?): Boolean { + if (token == null) { return false } - val controller = mediaControllerFactory.create(data.token) + val controller = mediaControllerFactory.create(token) val state = controller?.playbackState?.state return state != null && NotificationMediaManager.isActiveState(state) } @@ -542,7 +543,7 @@ class MediaDataManager @Inject constructor( /** * Are there any media entries, including resume controls? */ - fun hasAnyMedia() = mediaEntries.isNotEmpty() + fun hasAnyMedia() = if (useMediaResumption) mediaEntries.isNotEmpty() else hasActiveMedia() interface Listener { diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt index 2bd8c0cbeab2..7c5f0d1c2a16 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt @@ -1,5 +1,6 @@ package com.android.systemui.media +import android.graphics.PointF import android.graphics.Rect import android.view.View import android.view.View.OnAttachStateChangeListener @@ -20,8 +21,6 @@ class MediaHost @Inject constructor( var location: Int = -1 private set var visibleChangedListener: ((Boolean) -> Unit)? = null - var visible: Boolean = false - private set private val tmpLocationOnScreen: IntArray = intArrayOf(0, 0) @@ -109,16 +108,17 @@ class MediaHost @Inject constructor( } private fun updateViewVisibility() { - if (showsOnlyActiveMedia) { - visible = mediaDataManager.hasActiveMedia() + visible = if (showsOnlyActiveMedia) { + mediaDataManager.hasActiveMedia() } else { - visible = mediaDataManager.hasAnyMedia() + mediaDataManager.hasAnyMedia() } hostView.visibility = if (visible) View.VISIBLE else View.GONE visibleChangedListener?.invoke(visible) } class MediaHostStateHolder @Inject constructor() : MediaHostState { + private var gonePivot: PointF = PointF() override var measurementInput: MeasurementInput? = null set(value) { @@ -144,6 +144,25 @@ class MediaHost @Inject constructor( } } + override var visible: Boolean = true + set(value) { + if (field == value) { + return + } + field = value + changedListener?.invoke() + } + + override fun getPivotX(): Float = gonePivot.x + override fun getPivotY(): Float = gonePivot.y + override fun setGonePivot(x: Float, y: Float) { + if (gonePivot.equals(x, y)) { + return + } + gonePivot.set(x, y) + changedListener?.invoke() + } + /** * A listener for all changes. This won't be copied over when invoking [copy] */ @@ -157,6 +176,8 @@ class MediaHost @Inject constructor( mediaHostState.expansion = expansion mediaHostState.showsOnlyActiveMedia = showsOnlyActiveMedia mediaHostState.measurementInput = measurementInput?.copy() + mediaHostState.visible = visible + mediaHostState.gonePivot.set(gonePivot) return mediaHostState } @@ -173,6 +194,12 @@ class MediaHost @Inject constructor( if (showsOnlyActiveMedia != other.showsOnlyActiveMedia) { return false } + if (visible != other.visible) { + return false + } + if (!gonePivot.equals(other.getPivotX(), other.getPivotY())) { + return false + } return true } @@ -180,6 +207,8 @@ class MediaHost @Inject constructor( var result = measurementInput?.hashCode() ?: 0 result = 31 * result + expansion.hashCode() result = 31 * result + showsOnlyActiveMedia.hashCode() + result = 31 * result + if (visible) 1 else 2 + result = 31 * result + gonePivot.hashCode() return result } } @@ -194,7 +223,8 @@ interface MediaHostState { var measurementInput: MeasurementInput? /** - * The expansion of the player, 0 for fully collapsed, 1 for fully expanded + * The expansion of the player, 0 for fully collapsed (up to 3 actions), 1 for fully expanded + * (up to 5 actions.) */ var expansion: Float @@ -204,6 +234,30 @@ interface MediaHostState { var showsOnlyActiveMedia: Boolean /** + * If the view should be VISIBLE or GONE. + */ + var visible: Boolean + + /** + * Sets the pivot point when clipping the height or width. + * Clipping happens when animating visibility when we're visible in QS but not on QQS, + * for example. + */ + fun setGonePivot(x: Float, y: Float) + + /** + * x position of pivot, from 0 to 1 + * @see [setGonePivot] + */ + fun getPivotX(): Float + + /** + * y position of pivot, from 0 to 1 + * @see [setGonePivot] + */ + fun getPivotY(): Float + + /** * Get a copy of this view state, deepcopying all appropriate members */ fun copy(): MediaHostState diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt index 3c3f4a977ee7..e0155a1a558f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt @@ -43,6 +43,11 @@ class MediaTimeoutListener @Inject constructor( private val mediaListeners: MutableMap<String, PlaybackStateListener> = mutableMapOf() + /** + * Callback representing that a media object is now expired: + * @param token Media session unique identifier + * @param pauseTimeuot True when expired for {@code PAUSED_MEDIA_TIMEOUT} + */ lateinit var timeoutCallback: (String, Boolean) -> Unit override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { @@ -112,11 +117,11 @@ class MediaTimeoutListener @Inject constructor( } } - private fun expireMediaTimeout(mediaNotificationKey: String, reason: String) { + private fun expireMediaTimeout(mediaKey: String, reason: String) { cancellation?.apply { if (DEBUG) { Log.v(TAG, - "media timeout cancelled for $mediaNotificationKey, reason: $reason") + "media timeout cancelled for $mediaKey, reason: $reason") } run() } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt index e82bb402407e..90ccfc6ca725 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt @@ -17,20 +17,22 @@ package com.android.systemui.media import android.content.Context +import android.graphics.PointF import androidx.constraintlayout.widget.ConstraintSet import com.android.systemui.R +import com.android.systemui.util.animation.MeasurementOutput import com.android.systemui.util.animation.TransitionLayout import com.android.systemui.util.animation.TransitionLayoutController import com.android.systemui.util.animation.TransitionViewState -import com.android.systemui.util.animation.MeasurementOutput +import javax.inject.Inject /** * A class responsible for controlling a single instance of a media player handling interactions * with the view instance and keeping the media view states up to date. */ -class MediaViewController( +class MediaViewController @Inject constructor( context: Context, - val mediaHostStatesManager: MediaHostStatesManager + private val mediaHostStatesManager: MediaHostStatesManager ) { private var firstRefresh: Boolean = true @@ -44,7 +46,7 @@ class MediaViewController( /** * A map containing all viewStates for all locations of this mediaState */ - private val mViewStates: MutableMap<MediaHostState, TransitionViewState?> = mutableMapOf() + private val viewStates: MutableMap<MediaHostState, TransitionViewState?> = mutableMapOf() /** * The ending location of the view where it ends when all animations and transitions have @@ -69,6 +71,11 @@ class MediaViewController( private val tmpState = TransitionViewState() /** + * Temporary variable to avoid unnecessary allocations. + */ + private val tmpPoint = PointF() + + /** * A callback for media state changes */ val stateCallback = object : MediaHostStatesManager.Callback { @@ -125,7 +132,7 @@ class MediaViewController( * it's not available, it will recreate one by measuring, which may be expensive. */ private fun obtainViewState(state: MediaHostState): TransitionViewState? { - val viewState = mViewStates[state] + val viewState = viewStates[state] if (viewState != null) { // we already have cached this measurement, let's continue return viewState @@ -143,7 +150,7 @@ class MediaViewController( // We don't want to cache interpolated or null states as this could quickly fill up // our cache. We only cache the start and the end states since the interpolation // is cheap - mViewStates[state.copy()] = result + viewStates[state.copy()] = result } else { // This is an interpolated state val startState = state.copy().also { it.expansion = 0.0f } @@ -153,11 +160,13 @@ class MediaViewController( val startViewState = obtainViewState(startState) as TransitionViewState val endState = state.copy().also { it.expansion = 1.0f } val endViewState = obtainViewState(endState) as TransitionViewState + tmpPoint.set(startState.getPivotX(), startState.getPivotY()) result = TransitionViewState() layoutController.getInterpolatedState( startViewState, endViewState, state.expansion, + tmpPoint, result) } } else { @@ -213,11 +222,35 @@ class MediaViewController( val shouldAnimate = animateNextStateChange && !applyImmediately + var startHostState = mediaHostStatesManager.mediaHostStates[startLocation] + var endHostState = mediaHostStatesManager.mediaHostStates[endLocation] + var swappedStartState = false + var swappedEndState = false + + // if we're going from or to a non visible state, let's grab the visible one and animate + // the view being clipped instead. + if (endHostState?.visible != true) { + endHostState = startHostState + swappedEndState = true + } + if (startHostState?.visible != true) { + startHostState = endHostState + swappedStartState = true + } + if (startHostState == null || endHostState == null) { + return + } + + var endViewState = obtainViewState(endHostState) ?: return + if (swappedEndState) { + endViewState = endViewState.copy() + endViewState.height = 0 + } + // Obtain the view state that we'd want to be at the end // The view might not be bound yet or has never been measured and in that case will be // reset once the state is fully available - val endState = obtainViewStateForLocation(endLocation) ?: return - layoutController.setMeasureState(endState) + layoutController.setMeasureState(endViewState) // If the view isn't bound, we can drop the animation, otherwise we'll executute it animateNextStateChange = false @@ -225,24 +258,43 @@ class MediaViewController( return } - val startState = obtainViewStateForLocation(startLocation) + var startViewState = obtainViewState(startHostState) + if (swappedStartState) { + startViewState = startViewState?.copy() + startViewState?.height = 0 + } + val result: TransitionViewState? - if (transitionProgress == 1.0f || startState == null) { - result = endState + result = if (transitionProgress == 1.0f || startViewState == null) { + endViewState } else if (transitionProgress == 0.0f) { - result = startState + startViewState } else { - layoutController.getInterpolatedState(startState, endState, transitionProgress, - tmpState) - result = tmpState + if (swappedEndState || swappedStartState) { + tmpPoint.set(startHostState.getPivotX(), startHostState.getPivotY()) + } else { + tmpPoint.set(0.0f, 0.0f) + } + layoutController.getInterpolatedState(startViewState, endViewState, transitionProgress, + tmpPoint, tmpState) + tmpState } layoutController.setState(result, applyImmediately, shouldAnimate, animationDuration, animationDelay) } - private fun obtainViewStateForLocation(location: Int): TransitionViewState? { - val mediaState = mediaHostStatesManager.mediaHostStates[location] ?: return null - return obtainViewState(mediaState) + /** + * Retrieves the [TransitionViewState] and [MediaHostState] of a [@MediaLocation]. + * In the event of [location] not being visible, [locationWhenHidden] will be used instead. + * + * @param location Target + * @param locationWhenHidden Location that will be used when the target is not + * [MediaHost.visible] + * @return State require for executing a transition, and also the respective [MediaHost]. + */ + private fun obtainViewStateForLocation(@MediaLocation location: Int): TransitionViewState? { + val mediaHostState = mediaHostStatesManager.mediaHostStates[location] ?: return null + return obtainViewState(mediaHostState) } /** @@ -250,8 +302,7 @@ class MediaViewController( * This updates the width the view will me measured with. */ fun onLocationPreChange(@MediaLocation newLocation: Int) { - val viewState = obtainViewStateForLocation(newLocation) - viewState?.let { + obtainViewStateForLocation(newLocation)?.let { layoutController.setMeasureState(it) } } @@ -271,7 +322,7 @@ class MediaViewController( fun refreshState() { if (!firstRefresh) { // Let's clear all of our measurements and recreate them! - mViewStates.clear() + viewStates.clear() setCurrentState(currentStartLocation, currentEndLocation, currentTransitionProgress, applyImmediately = false) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 0fc3829fab66..5021e0019d57 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -235,6 +235,8 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne protected void initMediaHostState() { mMediaHost.setExpansion(1.0f); mMediaHost.setShowsOnlyActiveMedia(false); + // Reveal player with some parallax (1.0f would also work) + mMediaHost.setGonePivot(0.0f, 0.8f); mMediaHost.init(MediaHierarchyManager.LOCATION_QS); } diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt index 806d9d8e158a..eeca1e38abb0 100644 --- a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt @@ -17,6 +17,7 @@ package com.android.systemui.util.animation import android.content.Context +import android.graphics.PointF import android.graphics.Rect import android.util.AttributeSet import android.view.View @@ -151,6 +152,11 @@ class TransitionLayout @JvmOverloads constructor( val layoutTop = top setLeftTopRightBottom(layoutLeft, layoutTop, layoutLeft + currentState.width, layoutTop + currentState.height) + val bounds = clipBounds ?: Rect() + bounds.set(left, top, right, bottom) + clipBounds = bounds + translationX = currentState.translation.x + translationY = currentState.translation.y } /** @@ -234,11 +240,13 @@ class TransitionViewState { var widgetStates: MutableMap<Int, WidgetState> = mutableMapOf() var width: Int = 0 var height: Int = 0 + val translation = PointF() fun copy(reusedState: TransitionViewState? = null): TransitionViewState { // we need a deep copy of this, so we can't use a data class val copy = reusedState ?: TransitionViewState() copy.width = width copy.height = height + copy.translation.set(translation.x, translation.y) for (entry in widgetStates) { copy.widgetStates[entry.key] = entry.value.copy() } @@ -256,6 +264,7 @@ class TransitionViewState { } width = transitionLayout.measuredWidth height = transitionLayout.measuredHeight + translation.set(0.0f, 0.0f) } } diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt index 9ee141053861..b73aeb30009c 100644 --- a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt +++ b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt @@ -17,6 +17,7 @@ package com.android.systemui.util.animation import android.animation.ValueAnimator +import android.graphics.PointF import android.util.MathUtils import com.android.systemui.Interpolators @@ -43,6 +44,7 @@ open class TransitionLayoutController { private var currentState = TransitionViewState() private var animationStartState: TransitionViewState? = null private var state = TransitionViewState() + private var pivot = PointF() private var animator: ValueAnimator = ValueAnimator.ofFloat(0.0f, 1.0f) init { @@ -63,6 +65,7 @@ open class TransitionLayoutController { startState = animationStartState!!, endState = state, progress = animator.animatedFraction, + pivot = pivot, resultState = currentState) view.setState(currentState) } @@ -75,8 +78,10 @@ open class TransitionLayoutController { startState: TransitionViewState, endState: TransitionViewState, progress: Float, + pivot: PointF, resultState: TransitionViewState ) { + this.pivot.set(pivot) val view = transitionLayout ?: return val childCount = view.childCount for (i in 0 until childCount) { @@ -178,6 +183,8 @@ open class TransitionLayoutController { progress).toInt() height = MathUtils.lerp(startState.height.toFloat(), endState.height.toFloat(), progress).toInt() + translation.x = (endState.width - width) * pivot.x + translation.y = (endState.height - height) * pivot.y } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt index 737ced63eed0..e6a41fbac3b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt @@ -31,6 +31,7 @@ import android.widget.ImageButton import android.widget.ImageView import android.widget.SeekBar import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintSet import androidx.lifecycle.LiveData import androidx.test.filters.SmallTest import com.android.systemui.R @@ -75,9 +76,9 @@ public class MediaControlPanelTest : SysuiTestCase() { @Mock private lateinit var holder: PlayerViewHolder @Mock private lateinit var view: TransitionLayout - @Mock private lateinit var mediaHostStatesManager: MediaHostStatesManager @Mock private lateinit var seekBarViewModel: SeekBarViewModel @Mock private lateinit var seekBarData: LiveData<SeekBarViewModel.Progress> + @Mock private lateinit var mediaViewController: MediaViewController private lateinit var appIcon: ImageView private lateinit var appName: TextView private lateinit var albumView: ImageView @@ -104,8 +105,10 @@ public class MediaControlPanelTest : SysuiTestCase() { @Before fun setUp() { bgExecutor = FakeExecutor(FakeSystemClock()) + whenever(mediaViewController.expandedLayout).thenReturn(mock(ConstraintSet::class.java)) + whenever(mediaViewController.collapsedLayout).thenReturn(mock(ConstraintSet::class.java)) - player = MediaControlPanel(context, bgExecutor, activityStarter, mediaHostStatesManager, + player = MediaControlPanel(context, bgExecutor, activityStarter, mediaViewController, seekBarViewModel) whenever(seekBarViewModel.progress).thenReturn(seekBarData) @@ -172,7 +175,7 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun bindWhenUnattached() { val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, null, null, device, null) + emptyList(), PACKAGE, null, null, device, true, null) player.bind(state) assertThat(player.isPlaying()).isFalse() } @@ -181,7 +184,7 @@ public class MediaControlPanelTest : SysuiTestCase() { fun bindText() { player.attach(holder) val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, session.getSessionToken(), null, device, null) + emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null) player.bind(state) assertThat(appName.getText()).isEqualTo(APP) assertThat(titleText.getText()).isEqualTo(TITLE) @@ -192,7 +195,7 @@ public class MediaControlPanelTest : SysuiTestCase() { fun bindBackgroundColor() { player.attach(holder) val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, session.getSessionToken(), null, device, null) + emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null) player.bind(state) val list = ArgumentCaptor.forClass(ColorStateList::class.java) verify(view).setBackgroundTintList(list.capture()) @@ -203,7 +206,7 @@ public class MediaControlPanelTest : SysuiTestCase() { fun bindDevice() { player.attach(holder) val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, session.getSessionToken(), null, device, null) + emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null) player.bind(state) assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME) assertThat(seamless.isEnabled()).isTrue() @@ -213,7 +216,7 @@ public class MediaControlPanelTest : SysuiTestCase() { fun bindDisabledDevice() { player.attach(holder) val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, session.getSessionToken(), null, disabledDevice, null) + emptyList(), PACKAGE, session.getSessionToken(), null, disabledDevice, true, null) player.bind(state) assertThat(seamless.isEnabled()).isFalse() assertThat(seamlessText.getText()).isEqualTo(context.getResources().getString( @@ -224,7 +227,7 @@ public class MediaControlPanelTest : SysuiTestCase() { fun bindNullDevice() { player.attach(holder) val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, session.getSessionToken(), null, null, null) + emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null) player.bind(state) assertThat(seamless.isEnabled()).isTrue() assertThat(seamlessText.getText()).isEqualTo(context.getResources().getString( diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java index bed5c9eb6df5..618ee892b2b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java @@ -79,7 +79,8 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { mManager.addListener(mListener); mMediaData = new MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, - new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, null, KEY, false); + new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, true, null, KEY, + false); mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt index 22155b6cf953..6c7f2e8d7925 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt @@ -119,7 +119,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() { setStyle(Notification.MediaStyle().setMediaSession(session.getSessionToken())) } mediaData = MediaData(true, 0, PACKAGE, null, null, SESSION_TITLE, null, - emptyList(), emptyList(), PACKAGE, session.sessionToken, null, null, null) + emptyList(), emptyList(), PACKAGE, session.sessionToken, clickIntent = null, + device = null, active = true, resumeAction = null) } @After diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt index 7d44327b0d38..082c8ba8744f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt @@ -93,7 +93,8 @@ class MediaTimeoutListenerTest : SysuiTestCase() { } session.setActive(true) mediaData = MediaData(true, 0, PACKAGE, null, null, SESSION_TITLE, null, - emptyList(), emptyList(), PACKAGE, session.sessionToken, null, null, null) + emptyList(), emptyList(), PACKAGE, session.sessionToken, clickIntent = null, + device = null, active = true, resumeAction = null) } @Test |