summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaData.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaHost.kt66
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt93
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanel.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt3
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