Merge "Hook up media player resumption to UI" into rvc-dev am: df9b697804 am: c3a46cd1b0
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/11801909 Change-Id: I29097d0bacc2eb70e16931a6dd9f30f3c014ea4c
This commit is contained in:
@@ -96,13 +96,13 @@ public class MediaControlPanel {
|
|||||||
*/
|
*/
|
||||||
@Inject
|
@Inject
|
||||||
public MediaControlPanel(Context context, @Background Executor backgroundExecutor,
|
public MediaControlPanel(Context context, @Background Executor backgroundExecutor,
|
||||||
ActivityStarter activityStarter, MediaHostStatesManager mediaHostStatesManager,
|
ActivityStarter activityStarter, MediaViewController mediaViewController,
|
||||||
SeekBarViewModel seekBarViewModel) {
|
SeekBarViewModel seekBarViewModel) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mBackgroundExecutor = backgroundExecutor;
|
mBackgroundExecutor = backgroundExecutor;
|
||||||
mActivityStarter = activityStarter;
|
mActivityStarter = activityStarter;
|
||||||
mSeekBarViewModel = seekBarViewModel;
|
mSeekBarViewModel = seekBarViewModel;
|
||||||
mMediaViewController = new MediaViewController(context, mediaHostStatesManager);
|
mMediaViewController = mediaViewController;
|
||||||
loadDimens();
|
loadDimens();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,14 +365,6 @@ public class MediaControlPanel {
|
|||||||
return artwork;
|
return artwork;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the token for the current media session
|
|
||||||
* @return the token
|
|
||||||
*/
|
|
||||||
public MediaSession.Token getMediaSessionToken() {
|
|
||||||
return mToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current media controller
|
* Get the current media controller
|
||||||
* @return the controller
|
* @return the controller
|
||||||
@@ -381,25 +373,6 @@ public class MediaControlPanel {
|
|||||||
return mController;
|
return mController;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
* Check whether the media controlled by this player is currently playing
|
||||||
* @return whether it is playing, or false if no controller information
|
* @return whether it is playing, or false if no controller information
|
||||||
|
|||||||
@@ -25,19 +25,65 @@ import android.media.session.MediaSession
|
|||||||
data class MediaData(
|
data class MediaData(
|
||||||
val initialized: Boolean = false,
|
val initialized: Boolean = false,
|
||||||
val backgroundColor: Int,
|
val backgroundColor: Int,
|
||||||
|
/**
|
||||||
|
* App name that will be displayed on the player.
|
||||||
|
*/
|
||||||
val app: String?,
|
val app: String?,
|
||||||
|
/**
|
||||||
|
* Icon shown on player, close to app name.
|
||||||
|
*/
|
||||||
val appIcon: Drawable?,
|
val appIcon: Drawable?,
|
||||||
|
/**
|
||||||
|
* Artist name.
|
||||||
|
*/
|
||||||
val artist: CharSequence?,
|
val artist: CharSequence?,
|
||||||
|
/**
|
||||||
|
* Song name.
|
||||||
|
*/
|
||||||
val song: CharSequence?,
|
val song: CharSequence?,
|
||||||
|
/**
|
||||||
|
* Album artwork.
|
||||||
|
*/
|
||||||
val artwork: Icon?,
|
val artwork: Icon?,
|
||||||
|
/**
|
||||||
|
* List of actions that can be performed on the player: prev, next, play, pause, etc.
|
||||||
|
*/
|
||||||
val actions: List<MediaAction>,
|
val actions: List<MediaAction>,
|
||||||
|
/**
|
||||||
|
* Same as above, but shown on smaller versions of the player, like in QQS or keyguard.
|
||||||
|
*/
|
||||||
val actionsToShowInCompact: List<Int>,
|
val actionsToShowInCompact: List<Int>,
|
||||||
|
/**
|
||||||
|
* Package name of the app that's posting the media.
|
||||||
|
*/
|
||||||
val packageName: String,
|
val packageName: String,
|
||||||
|
/**
|
||||||
|
* Unique media session identifier.
|
||||||
|
*/
|
||||||
val token: MediaSession.Token?,
|
val token: MediaSession.Token?,
|
||||||
|
/**
|
||||||
|
* Action to perform when the player is tapped.
|
||||||
|
* This is unrelated to {@link #actions}.
|
||||||
|
*/
|
||||||
val clickIntent: PendingIntent?,
|
val clickIntent: PendingIntent?,
|
||||||
|
/**
|
||||||
|
* Where the media is playing: phone, headphones, ear buds, remote session.
|
||||||
|
*/
|
||||||
val device: MediaDeviceData?,
|
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?,
|
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
|
var hasCheckedForResume: Boolean = false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ private const val LUMINOSITY_THRESHOLD = 0.05f
|
|||||||
private const val SATURATION_MULTIPLIER = 0.8f
|
private const val SATURATION_MULTIPLIER = 0.8f
|
||||||
|
|
||||||
private val LOADING = MediaData(false, 0, null, null, null, null, null,
|
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 {
|
fun isMediaNotification(sbn: StatusBarNotification): Boolean {
|
||||||
if (!sbn.notification.hasMediaSession()) {
|
if (!sbn.notification.hasMediaSession()) {
|
||||||
@@ -88,12 +88,12 @@ fun isMediaNotification(sbn: StatusBarNotification): Boolean {
|
|||||||
class MediaDataManager @Inject constructor(
|
class MediaDataManager @Inject constructor(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val mediaControllerFactory: MediaControllerFactory,
|
private val mediaControllerFactory: MediaControllerFactory,
|
||||||
private val mediaTimeoutListener: MediaTimeoutListener,
|
|
||||||
private val notificationEntryManager: NotificationEntryManager,
|
private val notificationEntryManager: NotificationEntryManager,
|
||||||
private val mediaResumeListener: MediaResumeListener,
|
|
||||||
@Background private val backgroundExecutor: Executor,
|
@Background private val backgroundExecutor: Executor,
|
||||||
@Main private val foregroundExecutor: Executor,
|
@Main private val foregroundExecutor: Executor,
|
||||||
private val broadcastDispatcher: BroadcastDispatcher
|
broadcastDispatcher: BroadcastDispatcher,
|
||||||
|
mediaTimeoutListener: MediaTimeoutListener,
|
||||||
|
mediaResumeListener: MediaResumeListener
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val listeners: MutableSet<Listener> = mutableSetOf()
|
private val listeners: MutableSet<Listener> = mutableSetOf()
|
||||||
@@ -131,7 +131,6 @@ class MediaDataManager @Inject constructor(
|
|||||||
mediaTimeoutListener.timeoutCallback = { token: String, timedOut: Boolean ->
|
mediaTimeoutListener.timeoutCallback = { token: String, timedOut: Boolean ->
|
||||||
setTimedOut(token, timedOut) }
|
setTimedOut(token, timedOut) }
|
||||||
addListener(mediaTimeoutListener)
|
addListener(mediaTimeoutListener)
|
||||||
|
|
||||||
if (useMediaResumption) {
|
if (useMediaResumption) {
|
||||||
mediaResumeListener.addTrackToResumeCallback = { desc: MediaDescription,
|
mediaResumeListener.addTrackToResumeCallback = { desc: MediaDescription,
|
||||||
resumeAction: Runnable, token: MediaSession.Token, appName: String,
|
resumeAction: Runnable, token: MediaSession.Token, appName: String,
|
||||||
@@ -215,7 +214,7 @@ class MediaDataManager @Inject constructor(
|
|||||||
mediaEntries.put(packageName, resumeData)
|
mediaEntries.put(packageName, resumeData)
|
||||||
}
|
}
|
||||||
backgroundExecutor.execute {
|
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)
|
fun removeListener(listener: Listener) = listeners.remove(listener)
|
||||||
|
|
||||||
private fun setTimedOut(token: String, timedOut: Boolean) {
|
private fun setTimedOut(token: String, timedOut: Boolean) {
|
||||||
if (!timedOut) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
mediaEntries[token]?.let {
|
mediaEntries[token]?.let {
|
||||||
notificationEntryManager.removeNotification(it.notificationKey, null /* ranking */,
|
if (Utils.useMediaResumption(context)) {
|
||||||
UNDEFINED_DISMISS_REASON)
|
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,
|
desc: MediaDescription,
|
||||||
resumeAction: Runnable,
|
resumeAction: Runnable,
|
||||||
token: MediaSession.Token,
|
token: MediaSession.Token,
|
||||||
@@ -272,11 +276,6 @@ class MediaDataManager @Inject constructor(
|
|||||||
appIntent: PendingIntent,
|
appIntent: PendingIntent,
|
||||||
packageName: String
|
packageName: String
|
||||||
) {
|
) {
|
||||||
if (resumeAction == null) {
|
|
||||||
Log.e(TAG, "Resume action cannot be null")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TextUtils.isEmpty(desc.title)) {
|
if (TextUtils.isEmpty(desc.title)) {
|
||||||
Log.e(TAG, "Description incomplete")
|
Log.e(TAG, "Description incomplete")
|
||||||
return
|
return
|
||||||
@@ -298,8 +297,9 @@ class MediaDataManager @Inject constructor(
|
|||||||
val mediaAction = getResumeMediaAction(resumeAction)
|
val mediaAction = getResumeMediaAction(resumeAction)
|
||||||
foregroundExecutor.execute {
|
foregroundExecutor.execute {
|
||||||
onMediaDataLoaded(packageName, null, MediaData(true, Color.DKGRAY, appName,
|
onMediaDataLoaded(packageName, null, MediaData(true, Color.DKGRAY, appName,
|
||||||
null, desc.subtitle, desc.title, artworkIcon, listOf(mediaAction), listOf(0),
|
null, desc.subtitle, desc.title, artworkIcon, listOf(mediaAction), listOf(0),
|
||||||
packageName, token, appIntent, null, resumeAction, packageName))
|
packageName, token, appIntent, device = null, active = false,
|
||||||
|
resumeAction = resumeAction))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,7 +430,8 @@ class MediaDataManager @Inject constructor(
|
|||||||
foregroundExecutor.execute {
|
foregroundExecutor.execute {
|
||||||
onMediaDataLoaded(key, oldKey, MediaData(true, bgColor, app, smallIconDrawable, artist,
|
onMediaDataLoaded(key, oldKey, MediaData(true, bgColor, app, smallIconDrawable, artist,
|
||||||
song, artWorkIcon, actionIcons, actionsToShowCollapsed, sbn.packageName, token,
|
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?
|
* 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 {
|
fun isActive(token: MediaSession.Token?): Boolean {
|
||||||
if (data.token == null) {
|
if (token == null) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
val controller = mediaControllerFactory.create(data.token)
|
val controller = mediaControllerFactory.create(token)
|
||||||
val state = controller?.playbackState?.state
|
val state = controller?.playbackState?.state
|
||||||
return state != null && NotificationMediaManager.isActiveState(state)
|
return state != null && NotificationMediaManager.isActiveState(state)
|
||||||
}
|
}
|
||||||
@@ -542,7 +543,7 @@ class MediaDataManager @Inject constructor(
|
|||||||
/**
|
/**
|
||||||
* Are there any media entries, including resume controls?
|
* Are there any media entries, including resume controls?
|
||||||
*/
|
*/
|
||||||
fun hasAnyMedia() = mediaEntries.isNotEmpty()
|
fun hasAnyMedia() = if (useMediaResumption) mediaEntries.isNotEmpty() else hasActiveMedia()
|
||||||
|
|
||||||
interface Listener {
|
interface Listener {
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.android.systemui.media
|
package com.android.systemui.media
|
||||||
|
|
||||||
|
import android.graphics.PointF
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.View.OnAttachStateChangeListener
|
import android.view.View.OnAttachStateChangeListener
|
||||||
@@ -20,8 +21,6 @@ class MediaHost @Inject constructor(
|
|||||||
var location: Int = -1
|
var location: Int = -1
|
||||||
private set
|
private set
|
||||||
var visibleChangedListener: ((Boolean) -> Unit)? = null
|
var visibleChangedListener: ((Boolean) -> Unit)? = null
|
||||||
var visible: Boolean = false
|
|
||||||
private set
|
|
||||||
|
|
||||||
private val tmpLocationOnScreen: IntArray = intArrayOf(0, 0)
|
private val tmpLocationOnScreen: IntArray = intArrayOf(0, 0)
|
||||||
|
|
||||||
@@ -109,16 +108,17 @@ class MediaHost @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateViewVisibility() {
|
private fun updateViewVisibility() {
|
||||||
if (showsOnlyActiveMedia) {
|
visible = if (showsOnlyActiveMedia) {
|
||||||
visible = mediaDataManager.hasActiveMedia()
|
mediaDataManager.hasActiveMedia()
|
||||||
} else {
|
} else {
|
||||||
visible = mediaDataManager.hasAnyMedia()
|
mediaDataManager.hasAnyMedia()
|
||||||
}
|
}
|
||||||
hostView.visibility = if (visible) View.VISIBLE else View.GONE
|
hostView.visibility = if (visible) View.VISIBLE else View.GONE
|
||||||
visibleChangedListener?.invoke(visible)
|
visibleChangedListener?.invoke(visible)
|
||||||
}
|
}
|
||||||
|
|
||||||
class MediaHostStateHolder @Inject constructor() : MediaHostState {
|
class MediaHostStateHolder @Inject constructor() : MediaHostState {
|
||||||
|
private var gonePivot: PointF = PointF()
|
||||||
|
|
||||||
override var measurementInput: MeasurementInput? = null
|
override var measurementInput: MeasurementInput? = null
|
||||||
set(value) {
|
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]
|
* 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.expansion = expansion
|
||||||
mediaHostState.showsOnlyActiveMedia = showsOnlyActiveMedia
|
mediaHostState.showsOnlyActiveMedia = showsOnlyActiveMedia
|
||||||
mediaHostState.measurementInput = measurementInput?.copy()
|
mediaHostState.measurementInput = measurementInput?.copy()
|
||||||
|
mediaHostState.visible = visible
|
||||||
|
mediaHostState.gonePivot.set(gonePivot)
|
||||||
return mediaHostState
|
return mediaHostState
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,6 +194,12 @@ class MediaHost @Inject constructor(
|
|||||||
if (showsOnlyActiveMedia != other.showsOnlyActiveMedia) {
|
if (showsOnlyActiveMedia != other.showsOnlyActiveMedia) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if (visible != other.visible) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (!gonePivot.equals(other.getPivotX(), other.getPivotY())) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,6 +207,8 @@ class MediaHost @Inject constructor(
|
|||||||
var result = measurementInput?.hashCode() ?: 0
|
var result = measurementInput?.hashCode() ?: 0
|
||||||
result = 31 * result + expansion.hashCode()
|
result = 31 * result + expansion.hashCode()
|
||||||
result = 31 * result + showsOnlyActiveMedia.hashCode()
|
result = 31 * result + showsOnlyActiveMedia.hashCode()
|
||||||
|
result = 31 * result + if (visible) 1 else 2
|
||||||
|
result = 31 * result + gonePivot.hashCode()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,7 +223,8 @@ interface MediaHostState {
|
|||||||
var measurementInput: MeasurementInput?
|
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
|
var expansion: Float
|
||||||
|
|
||||||
@@ -203,6 +233,30 @@ interface MediaHostState {
|
|||||||
*/
|
*/
|
||||||
var showsOnlyActiveMedia: Boolean
|
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
|
* Get a copy of this view state, deepcopying all appropriate members
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -43,6 +43,11 @@ class MediaTimeoutListener @Inject constructor(
|
|||||||
|
|
||||||
private val mediaListeners: MutableMap<String, PlaybackStateListener> = mutableMapOf()
|
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
|
lateinit var timeoutCallback: (String, Boolean) -> Unit
|
||||||
|
|
||||||
override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
|
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 {
|
cancellation?.apply {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.v(TAG,
|
Log.v(TAG,
|
||||||
"media timeout cancelled for $mediaNotificationKey, reason: $reason")
|
"media timeout cancelled for $mediaKey, reason: $reason")
|
||||||
}
|
}
|
||||||
run()
|
run()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,20 +17,22 @@
|
|||||||
package com.android.systemui.media
|
package com.android.systemui.media
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.PointF
|
||||||
import androidx.constraintlayout.widget.ConstraintSet
|
import androidx.constraintlayout.widget.ConstraintSet
|
||||||
import com.android.systemui.R
|
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.TransitionLayout
|
||||||
import com.android.systemui.util.animation.TransitionLayoutController
|
import com.android.systemui.util.animation.TransitionLayoutController
|
||||||
import com.android.systemui.util.animation.TransitionViewState
|
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
|
* 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.
|
* with the view instance and keeping the media view states up to date.
|
||||||
*/
|
*/
|
||||||
class MediaViewController(
|
class MediaViewController @Inject constructor(
|
||||||
context: Context,
|
context: Context,
|
||||||
val mediaHostStatesManager: MediaHostStatesManager
|
private val mediaHostStatesManager: MediaHostStatesManager
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private var firstRefresh: Boolean = true
|
private var firstRefresh: Boolean = true
|
||||||
@@ -44,7 +46,7 @@ class MediaViewController(
|
|||||||
/**
|
/**
|
||||||
* A map containing all viewStates for all locations of this mediaState
|
* 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
|
* The ending location of the view where it ends when all animations and transitions have
|
||||||
@@ -68,6 +70,11 @@ class MediaViewController(
|
|||||||
*/
|
*/
|
||||||
private val tmpState = TransitionViewState()
|
private val tmpState = TransitionViewState()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporary variable to avoid unnecessary allocations.
|
||||||
|
*/
|
||||||
|
private val tmpPoint = PointF()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A callback for media state changes
|
* A callback for media state changes
|
||||||
*/
|
*/
|
||||||
@@ -125,7 +132,7 @@ class MediaViewController(
|
|||||||
* it's not available, it will recreate one by measuring, which may be expensive.
|
* it's not available, it will recreate one by measuring, which may be expensive.
|
||||||
*/
|
*/
|
||||||
private fun obtainViewState(state: MediaHostState): TransitionViewState? {
|
private fun obtainViewState(state: MediaHostState): TransitionViewState? {
|
||||||
val viewState = mViewStates[state]
|
val viewState = viewStates[state]
|
||||||
if (viewState != null) {
|
if (viewState != null) {
|
||||||
// we already have cached this measurement, let's continue
|
// we already have cached this measurement, let's continue
|
||||||
return viewState
|
return viewState
|
||||||
@@ -143,7 +150,7 @@ class MediaViewController(
|
|||||||
// We don't want to cache interpolated or null states as this could quickly fill up
|
// 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
|
// our cache. We only cache the start and the end states since the interpolation
|
||||||
// is cheap
|
// is cheap
|
||||||
mViewStates[state.copy()] = result
|
viewStates[state.copy()] = result
|
||||||
} else {
|
} else {
|
||||||
// This is an interpolated state
|
// This is an interpolated state
|
||||||
val startState = state.copy().also { it.expansion = 0.0f }
|
val startState = state.copy().also { it.expansion = 0.0f }
|
||||||
@@ -153,11 +160,13 @@ class MediaViewController(
|
|||||||
val startViewState = obtainViewState(startState) as TransitionViewState
|
val startViewState = obtainViewState(startState) as TransitionViewState
|
||||||
val endState = state.copy().also { it.expansion = 1.0f }
|
val endState = state.copy().also { it.expansion = 1.0f }
|
||||||
val endViewState = obtainViewState(endState) as TransitionViewState
|
val endViewState = obtainViewState(endState) as TransitionViewState
|
||||||
|
tmpPoint.set(startState.getPivotX(), startState.getPivotY())
|
||||||
result = TransitionViewState()
|
result = TransitionViewState()
|
||||||
layoutController.getInterpolatedState(
|
layoutController.getInterpolatedState(
|
||||||
startViewState,
|
startViewState,
|
||||||
endViewState,
|
endViewState,
|
||||||
state.expansion,
|
state.expansion,
|
||||||
|
tmpPoint,
|
||||||
result)
|
result)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -213,11 +222,35 @@ class MediaViewController(
|
|||||||
|
|
||||||
val shouldAnimate = animateNextStateChange && !applyImmediately
|
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
|
// 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
|
// 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
|
// reset once the state is fully available
|
||||||
val endState = obtainViewStateForLocation(endLocation) ?: return
|
layoutController.setMeasureState(endViewState)
|
||||||
layoutController.setMeasureState(endState)
|
|
||||||
|
|
||||||
// If the view isn't bound, we can drop the animation, otherwise we'll executute it
|
// If the view isn't bound, we can drop the animation, otherwise we'll executute it
|
||||||
animateNextStateChange = false
|
animateNextStateChange = false
|
||||||
@@ -225,24 +258,43 @@ class MediaViewController(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val startState = obtainViewStateForLocation(startLocation)
|
var startViewState = obtainViewState(startHostState)
|
||||||
|
if (swappedStartState) {
|
||||||
|
startViewState = startViewState?.copy()
|
||||||
|
startViewState?.height = 0
|
||||||
|
}
|
||||||
|
|
||||||
val result: TransitionViewState?
|
val result: TransitionViewState?
|
||||||
if (transitionProgress == 1.0f || startState == null) {
|
result = if (transitionProgress == 1.0f || startViewState == null) {
|
||||||
result = endState
|
endViewState
|
||||||
} else if (transitionProgress == 0.0f) {
|
} else if (transitionProgress == 0.0f) {
|
||||||
result = startState
|
startViewState
|
||||||
} else {
|
} else {
|
||||||
layoutController.getInterpolatedState(startState, endState, transitionProgress,
|
if (swappedEndState || swappedStartState) {
|
||||||
tmpState)
|
tmpPoint.set(startHostState.getPivotX(), startHostState.getPivotY())
|
||||||
result = tmpState
|
} else {
|
||||||
|
tmpPoint.set(0.0f, 0.0f)
|
||||||
|
}
|
||||||
|
layoutController.getInterpolatedState(startViewState, endViewState, transitionProgress,
|
||||||
|
tmpPoint, tmpState)
|
||||||
|
tmpState
|
||||||
}
|
}
|
||||||
layoutController.setState(result, applyImmediately, shouldAnimate, animationDuration,
|
layoutController.setState(result, applyImmediately, shouldAnimate, animationDuration,
|
||||||
animationDelay)
|
animationDelay)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun obtainViewStateForLocation(location: Int): TransitionViewState? {
|
/**
|
||||||
val mediaState = mediaHostStatesManager.mediaHostStates[location] ?: return null
|
* Retrieves the [TransitionViewState] and [MediaHostState] of a [@MediaLocation].
|
||||||
return obtainViewState(mediaState)
|
* 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.
|
* This updates the width the view will me measured with.
|
||||||
*/
|
*/
|
||||||
fun onLocationPreChange(@MediaLocation newLocation: Int) {
|
fun onLocationPreChange(@MediaLocation newLocation: Int) {
|
||||||
val viewState = obtainViewStateForLocation(newLocation)
|
obtainViewStateForLocation(newLocation)?.let {
|
||||||
viewState?.let {
|
|
||||||
layoutController.setMeasureState(it)
|
layoutController.setMeasureState(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -271,7 +322,7 @@ class MediaViewController(
|
|||||||
fun refreshState() {
|
fun refreshState() {
|
||||||
if (!firstRefresh) {
|
if (!firstRefresh) {
|
||||||
// Let's clear all of our measurements and recreate them!
|
// Let's clear all of our measurements and recreate them!
|
||||||
mViewStates.clear()
|
viewStates.clear()
|
||||||
setCurrentState(currentStartLocation, currentEndLocation, currentTransitionProgress,
|
setCurrentState(currentStartLocation, currentEndLocation, currentTransitionProgress,
|
||||||
applyImmediately = false)
|
applyImmediately = false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -235,6 +235,8 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
|
|||||||
protected void initMediaHostState() {
|
protected void initMediaHostState() {
|
||||||
mMediaHost.setExpansion(1.0f);
|
mMediaHost.setExpansion(1.0f);
|
||||||
mMediaHost.setShowsOnlyActiveMedia(false);
|
mMediaHost.setShowsOnlyActiveMedia(false);
|
||||||
|
// Reveal player with some parallax (1.0f would also work)
|
||||||
|
mMediaHost.setGonePivot(0.0f, 0.8f);
|
||||||
mMediaHost.init(MediaHierarchyManager.LOCATION_QS);
|
mMediaHost.init(MediaHierarchyManager.LOCATION_QS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package com.android.systemui.util.animation
|
package com.android.systemui.util.animation
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.PointF
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@@ -151,6 +152,11 @@ class TransitionLayout @JvmOverloads constructor(
|
|||||||
val layoutTop = top
|
val layoutTop = top
|
||||||
setLeftTopRightBottom(layoutLeft, layoutTop, layoutLeft + currentState.width,
|
setLeftTopRightBottom(layoutLeft, layoutTop, layoutLeft + currentState.width,
|
||||||
layoutTop + currentState.height)
|
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 widgetStates: MutableMap<Int, WidgetState> = mutableMapOf()
|
||||||
var width: Int = 0
|
var width: Int = 0
|
||||||
var height: Int = 0
|
var height: Int = 0
|
||||||
|
val translation = PointF()
|
||||||
fun copy(reusedState: TransitionViewState? = null): TransitionViewState {
|
fun copy(reusedState: TransitionViewState? = null): TransitionViewState {
|
||||||
// we need a deep copy of this, so we can't use a data class
|
// we need a deep copy of this, so we can't use a data class
|
||||||
val copy = reusedState ?: TransitionViewState()
|
val copy = reusedState ?: TransitionViewState()
|
||||||
copy.width = width
|
copy.width = width
|
||||||
copy.height = height
|
copy.height = height
|
||||||
|
copy.translation.set(translation.x, translation.y)
|
||||||
for (entry in widgetStates) {
|
for (entry in widgetStates) {
|
||||||
copy.widgetStates[entry.key] = entry.value.copy()
|
copy.widgetStates[entry.key] = entry.value.copy()
|
||||||
}
|
}
|
||||||
@@ -256,6 +264,7 @@ class TransitionViewState {
|
|||||||
}
|
}
|
||||||
width = transitionLayout.measuredWidth
|
width = transitionLayout.measuredWidth
|
||||||
height = transitionLayout.measuredHeight
|
height = transitionLayout.measuredHeight
|
||||||
|
translation.set(0.0f, 0.0f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package com.android.systemui.util.animation
|
package com.android.systemui.util.animation
|
||||||
|
|
||||||
import android.animation.ValueAnimator
|
import android.animation.ValueAnimator
|
||||||
|
import android.graphics.PointF
|
||||||
import android.util.MathUtils
|
import android.util.MathUtils
|
||||||
import com.android.systemui.Interpolators
|
import com.android.systemui.Interpolators
|
||||||
|
|
||||||
@@ -43,6 +44,7 @@ open class TransitionLayoutController {
|
|||||||
private var currentState = TransitionViewState()
|
private var currentState = TransitionViewState()
|
||||||
private var animationStartState: TransitionViewState? = null
|
private var animationStartState: TransitionViewState? = null
|
||||||
private var state = TransitionViewState()
|
private var state = TransitionViewState()
|
||||||
|
private var pivot = PointF()
|
||||||
private var animator: ValueAnimator = ValueAnimator.ofFloat(0.0f, 1.0f)
|
private var animator: ValueAnimator = ValueAnimator.ofFloat(0.0f, 1.0f)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -63,6 +65,7 @@ open class TransitionLayoutController {
|
|||||||
startState = animationStartState!!,
|
startState = animationStartState!!,
|
||||||
endState = state,
|
endState = state,
|
||||||
progress = animator.animatedFraction,
|
progress = animator.animatedFraction,
|
||||||
|
pivot = pivot,
|
||||||
resultState = currentState)
|
resultState = currentState)
|
||||||
view.setState(currentState)
|
view.setState(currentState)
|
||||||
}
|
}
|
||||||
@@ -75,8 +78,10 @@ open class TransitionLayoutController {
|
|||||||
startState: TransitionViewState,
|
startState: TransitionViewState,
|
||||||
endState: TransitionViewState,
|
endState: TransitionViewState,
|
||||||
progress: Float,
|
progress: Float,
|
||||||
|
pivot: PointF,
|
||||||
resultState: TransitionViewState
|
resultState: TransitionViewState
|
||||||
) {
|
) {
|
||||||
|
this.pivot.set(pivot)
|
||||||
val view = transitionLayout ?: return
|
val view = transitionLayout ?: return
|
||||||
val childCount = view.childCount
|
val childCount = view.childCount
|
||||||
for (i in 0 until childCount) {
|
for (i in 0 until childCount) {
|
||||||
@@ -178,6 +183,8 @@ open class TransitionLayoutController {
|
|||||||
progress).toInt()
|
progress).toInt()
|
||||||
height = MathUtils.lerp(startState.height.toFloat(), endState.height.toFloat(),
|
height = MathUtils.lerp(startState.height.toFloat(), endState.height.toFloat(),
|
||||||
progress).toInt()
|
progress).toInt()
|
||||||
|
translation.x = (endState.width - width) * pivot.x
|
||||||
|
translation.y = (endState.height - height) * pivot.y
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import android.widget.ImageButton
|
|||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.SeekBar
|
import android.widget.SeekBar
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.constraintlayout.widget.ConstraintSet
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
@@ -75,9 +76,9 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Mock private lateinit var holder: PlayerViewHolder
|
@Mock private lateinit var holder: PlayerViewHolder
|
||||||
@Mock private lateinit var view: TransitionLayout
|
@Mock private lateinit var view: TransitionLayout
|
||||||
@Mock private lateinit var mediaHostStatesManager: MediaHostStatesManager
|
|
||||||
@Mock private lateinit var seekBarViewModel: SeekBarViewModel
|
@Mock private lateinit var seekBarViewModel: SeekBarViewModel
|
||||||
@Mock private lateinit var seekBarData: LiveData<SeekBarViewModel.Progress>
|
@Mock private lateinit var seekBarData: LiveData<SeekBarViewModel.Progress>
|
||||||
|
@Mock private lateinit var mediaViewController: MediaViewController
|
||||||
private lateinit var appIcon: ImageView
|
private lateinit var appIcon: ImageView
|
||||||
private lateinit var appName: TextView
|
private lateinit var appName: TextView
|
||||||
private lateinit var albumView: ImageView
|
private lateinit var albumView: ImageView
|
||||||
@@ -104,8 +105,10 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
bgExecutor = FakeExecutor(FakeSystemClock())
|
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)
|
seekBarViewModel)
|
||||||
whenever(seekBarViewModel.progress).thenReturn(seekBarData)
|
whenever(seekBarViewModel.progress).thenReturn(seekBarData)
|
||||||
|
|
||||||
@@ -172,7 +175,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun bindWhenUnattached() {
|
fun bindWhenUnattached() {
|
||||||
val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
|
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)
|
player.bind(state)
|
||||||
assertThat(player.isPlaying()).isFalse()
|
assertThat(player.isPlaying()).isFalse()
|
||||||
}
|
}
|
||||||
@@ -181,7 +184,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
fun bindText() {
|
fun bindText() {
|
||||||
player.attach(holder)
|
player.attach(holder)
|
||||||
val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
|
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)
|
player.bind(state)
|
||||||
assertThat(appName.getText()).isEqualTo(APP)
|
assertThat(appName.getText()).isEqualTo(APP)
|
||||||
assertThat(titleText.getText()).isEqualTo(TITLE)
|
assertThat(titleText.getText()).isEqualTo(TITLE)
|
||||||
@@ -192,7 +195,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
fun bindBackgroundColor() {
|
fun bindBackgroundColor() {
|
||||||
player.attach(holder)
|
player.attach(holder)
|
||||||
val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
|
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)
|
player.bind(state)
|
||||||
val list = ArgumentCaptor.forClass(ColorStateList::class.java)
|
val list = ArgumentCaptor.forClass(ColorStateList::class.java)
|
||||||
verify(view).setBackgroundTintList(list.capture())
|
verify(view).setBackgroundTintList(list.capture())
|
||||||
@@ -203,7 +206,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
fun bindDevice() {
|
fun bindDevice() {
|
||||||
player.attach(holder)
|
player.attach(holder)
|
||||||
val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
|
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)
|
player.bind(state)
|
||||||
assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
|
assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
|
||||||
assertThat(seamless.isEnabled()).isTrue()
|
assertThat(seamless.isEnabled()).isTrue()
|
||||||
@@ -213,7 +216,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
fun bindDisabledDevice() {
|
fun bindDisabledDevice() {
|
||||||
player.attach(holder)
|
player.attach(holder)
|
||||||
val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
|
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)
|
player.bind(state)
|
||||||
assertThat(seamless.isEnabled()).isFalse()
|
assertThat(seamless.isEnabled()).isFalse()
|
||||||
assertThat(seamlessText.getText()).isEqualTo(context.getResources().getString(
|
assertThat(seamlessText.getText()).isEqualTo(context.getResources().getString(
|
||||||
@@ -224,7 +227,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
fun bindNullDevice() {
|
fun bindNullDevice() {
|
||||||
player.attach(holder)
|
player.attach(holder)
|
||||||
val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
|
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)
|
player.bind(state)
|
||||||
assertThat(seamless.isEnabled()).isTrue()
|
assertThat(seamless.isEnabled()).isTrue()
|
||||||
assertThat(seamlessText.getText()).isEqualTo(context.getResources().getString(
|
assertThat(seamlessText.getText()).isEqualTo(context.getResources().getString(
|
||||||
|
|||||||
@@ -79,7 +79,8 @@ public class MediaDataCombineLatestTest extends SysuiTestCase {
|
|||||||
mManager.addListener(mListener);
|
mManager.addListener(mListener);
|
||||||
|
|
||||||
mMediaData = new MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null,
|
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);
|
mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -119,7 +119,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
|
|||||||
setStyle(Notification.MediaStyle().setMediaSession(session.getSessionToken()))
|
setStyle(Notification.MediaStyle().setMediaSession(session.getSessionToken()))
|
||||||
}
|
}
|
||||||
mediaData = MediaData(true, 0, PACKAGE, null, null, SESSION_TITLE, null,
|
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
|
@After
|
||||||
|
|||||||
@@ -93,7 +93,8 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
session.setActive(true)
|
session.setActive(true)
|
||||||
mediaData = MediaData(true, 0, PACKAGE, null, null, SESSION_TITLE, null,
|
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
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user