On Smartspace removal update, only dismiss media recommendation/player

when it's invisible to users.

1. Add the param "immediately" to #onMediaDataLoaded and
on the underlying Smartspace data's removal;
2. #onSmartspaceMediaDataRemoved should only set "immediately" to true
on the user's interaction (e.g. Swipe-to-dismiss);
3. Add #SmartspaceMediaData to preserve the information for a
recommendation's view.
    - This data class is to introduce the field - "isActive", which will be set to false when a) The underlying
Smartspace data is removed; b) The Smartspace rec card is dismissed by
the user.
    - This appraoch is used instead of marking the SmartspaceTarget
instance to be null: the instance needs to be kept around to block the
duplicate Smartspace updates.

Fixes: 187219674
Fixes: 185787575
Test: Local builds
Change-Id: I19c398fa617749a9e643db33865fd2c5f8f9bdc4
This commit is contained in:
cecilia
2021-05-13 23:24:52 -04:00
committed by Cecilia Hong
parent 9787f77678
commit 38c6f2bf57
17 changed files with 470 additions and 266 deletions

View File

@@ -1,6 +1,5 @@
package com.android.systemui.media
import android.app.smartspace.SmartspaceTarget
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
@@ -184,7 +183,12 @@ class MediaCarouselController @Inject constructor(
visualStabilityManager.addReorderingAllowedCallback(visualStabilityCallback,
true /* persistent */)
mediaManager.addListener(object : MediaDataManager.Listener {
override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
override fun onMediaDataLoaded(
key: String,
oldKey: String?,
data: MediaData,
immediately: Boolean
) {
if (addOrUpdatePlayer(key, oldKey, data)) {
MediaPlayerData.getMediaPlayer(key, null)?.let {
logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
@@ -210,19 +214,23 @@ class MediaCarouselController @Inject constructor(
override fun onSmartspaceMediaDataLoaded(
key: String,
data: SmartspaceTarget,
data: SmartspaceMediaData,
shouldPrioritize: Boolean
) {
Log.d(TAG, "My Smartspace media update is here")
addSmartspaceMediaRecommendations(key, data, shouldPrioritize)
MediaPlayerData.getMediaPlayer(key, null)?.let {
logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
it.mInstanceId,
/* isRecommendationCard */ true,
it.surfaceForSmartspaceLogging)
}
if (mediaCarouselScrollHandler.visibleToUser) {
logSmartspaceImpression()
if (data.isActive) {
addSmartspaceMediaRecommendations(key, data, shouldPrioritize)
MediaPlayerData.getMediaPlayer(key, null)?.let {
logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
it.mInstanceId,
/* isRecommendationCard */ true,
it.surfaceForSmartspaceLogging)
}
if (mediaCarouselScrollHandler.visibleToUser) {
logSmartspaceImpression()
}
} else {
onSmartspaceMediaDataRemoved(data.targetId, immediately = true)
}
}
@@ -230,9 +238,13 @@ class MediaCarouselController @Inject constructor(
removePlayer(key)
}
override fun onSmartspaceMediaDataRemoved(key: String) {
override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
Log.d(TAG, "My Smartspace media removal request is received")
removePlayer(key)
if (immediately || visualStabilityManager.isReorderingAllowed) {
onMediaDataRemoved(key)
} else {
keysNeedRemoval.add(key)
}
}
})
mediaFrame.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
@@ -287,7 +299,7 @@ class MediaCarouselController @Inject constructor(
// Automatically scroll to the active player if needed
if (shouldScrollToActivePlayer) {
shouldScrollToActivePlayer = false
val activeMediaIndex = MediaPlayerData.getActiveMediaIndex()
val activeMediaIndex = MediaPlayerData.activeMediaIndex()
if (activeMediaIndex != -1) {
mediaCarouselScrollHandler.scrollToActivePlayer(activeMediaIndex)
}
@@ -333,7 +345,7 @@ class MediaCarouselController @Inject constructor(
private fun addSmartspaceMediaRecommendations(
key: String,
data: SmartspaceTarget,
data: SmartspaceMediaData,
shouldPrioritize: Boolean
) {
Log.d(TAG, "Updating smartspace target in carousel")
@@ -342,6 +354,11 @@ class MediaCarouselController @Inject constructor(
return
}
val existingSmartspaceMediaKey = MediaPlayerData.smartspaceMediaKey()
existingSmartspaceMediaKey?.let {
MediaPlayerData.removeMediaPlayer(existingSmartspaceMediaKey)
}
var newRecs = mediaControlPanelFactory.get()
newRecs.attachRecommendation(
RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent))
@@ -349,7 +366,7 @@ class MediaCarouselController @Inject constructor(
val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT)
newRecs.recommendationViewHolder?.recommendations?.setLayoutParams(lp)
newRecs.bindRecommendation(data, bgColor)
newRecs.bindRecommendation(data.copy(backgroundColor = bgColor))
MediaPlayerData.addMediaRecommendation(key, newRecs, shouldPrioritize)
updatePlayerToState(newRecs, noAnimation = true)
reorderAllPlayers()
@@ -378,11 +395,11 @@ class MediaCarouselController @Inject constructor(
if (dismissMediaData) {
// Inform the media manager of a potentially late dismissal
mediaManager.dismissMediaData(key, 0L /* delaye */)
mediaManager.dismissMediaData(key, delay = 0L)
}
if (dismissRecommendation) {
// Inform the media manager of a potentially late dismissal
mediaManager.dismissSmartspaceRecommendation(0L /* delay */)
mediaManager.dismissSmartspaceRecommendation(key, delay = 0L)
}
}
}
@@ -392,7 +409,7 @@ class MediaCarouselController @Inject constructor(
pageIndicator.tintList = ColorStateList.valueOf(getForegroundColor())
MediaPlayerData.mediaData().forEach { (key, data) ->
removePlayer(key, dismissMediaData = false)
removePlayer(key, dismissMediaData = false, dismissRecommendation = false)
addOrUpdatePlayer(key = key, oldKey = null, data = data)
}
}
@@ -732,7 +749,7 @@ internal object MediaPlayerData {
fun players() = mediaPlayers.values
/** Returns the index of the first non-timeout media. */
fun getActiveMediaIndex(): Int {
fun activeMediaIndex(): Int {
mediaPlayers.entries.forEachIndexed { index, e ->
if (!e.key.isSsMediaRec && e.key.data.active) {
return index
@@ -741,6 +758,16 @@ internal object MediaPlayerData {
return -1
}
/** Returns the existing Smartspace target id. */
fun smartspaceMediaKey(): String? {
mediaData.entries.forEach { e ->
if (e.value.isSsMediaRec) {
return e.key
}
}
return null
}
fun playerKeys() = mediaPlayers.keys
@VisibleForTesting

View File

@@ -560,12 +560,10 @@ class MediaCarouselScrollHandler(
}
fun scrollToActivePlayer(activePlayerIndex: Int) {
var destIndex = activePlayerIndex
destIndex = Math.min(mediaContent.getChildCount() - 1, destIndex)
val destIndex = Math.min(mediaContent.getChildCount() - 1, activePlayerIndex)
val view = mediaContent.getChildAt(destIndex)
// We need to post this to wait for the active player becomes visible.
mainExecutor.executeDelayed({
visibleMediaIndex = activePlayerIndex
scrollView.smoothScrollTo(view.left, scrollView.scrollY)
}, SCROLL_DELAY)
}

View File

@@ -20,7 +20,6 @@ import static android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS;
import android.app.PendingIntent;
import android.app.smartspace.SmartspaceAction;
import android.app.smartspace.SmartspaceTarget;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -34,7 +33,6 @@ import android.graphics.drawable.Icon;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
import android.os.Bundle;
import android.text.Layout;
import android.util.Log;
import android.view.View;
@@ -74,7 +72,6 @@ import kotlin.Unit;
public class MediaControlPanel {
private static final String TAG = "MediaControlPanel";
private static final float DISABLED_ALPHA = 0.38f;
private static final String EXTRAS_MEDIA_SOURCE_PACKAGE_NAME = "package_name";
private static final String EXTRAS_SMARTSPACE_INTENT =
"com.google.android.apps.gsa.smartspace.extra.SMARTSPACE_INTENT";
private static final String KEY_SMARTSPACE_OPEN_IN_FOREGROUND = "KEY_OPEN_IN_FOREGROUND";
@@ -493,27 +490,30 @@ public class MediaControlPanel {
};
}
/** Bind this recommendation view based on the data given. */
public void bindRecommendation(@NonNull SmartspaceTarget target, @NonNull int backgroundColor) {
/** Bind this recommendation view based on the given data. */
public void bindRecommendation(@NonNull SmartspaceMediaData data) {
if (mRecommendationViewHolder == null) {
return;
}
mInstanceId = target.getSmartspaceTargetId().hashCode();
mInstanceId = data.getTargetId().hashCode();
mBackgroundColor = data.getBackgroundColor();
mRecommendationViewHolder.getRecommendations()
.setBackgroundTintList(ColorStateList.valueOf(backgroundColor));
mBackgroundColor = backgroundColor;
.setBackgroundTintList(ColorStateList.valueOf(mBackgroundColor));
List<SmartspaceAction> mediaRecommendationList = target.getIconGrid();
List<SmartspaceAction> mediaRecommendationList = data.getRecommendations();
if (mediaRecommendationList == null || mediaRecommendationList.isEmpty()) {
Log.w(TAG, "Empty media recommendations");
return;
}
// Set up recommendation card's header.
ApplicationInfo applicationInfo = getApplicationInfo(target);
if (applicationInfo == null) {
Log.w(TAG, "No valid application info is found for media recommendations");
ApplicationInfo applicationInfo = null;
try {
applicationInfo = mContext.getPackageManager()
.getApplicationInfo(data.getPackageName(), 0 /* flags */);
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Fail to get media recommendation's app info", e);
return;
}
@@ -531,7 +531,7 @@ public class MediaControlPanel {
}
// Set up media card's tap action if applicable.
setSmartspaceRecItemOnClickListener(
mRecommendationViewHolder.getRecommendations(), target.getBaseAction());
mRecommendationViewHolder.getRecommendations(), data.getCardAction());
List<ImageView> mediaCoverItems = mRecommendationViewHolder.getMediaCoverItems();
List<Integer> mediaCoverItemsResIds = mRecommendationViewHolder.getMediaCoverItemsResIds();
@@ -574,7 +574,7 @@ public class MediaControlPanel {
/* isRecommendationCard */ true);
closeGuts();
mMediaDataManagerLazy.get().dismissSmartspaceRecommendation(
MediaViewController.GUTS_ANIMATION_DURATION + 100L);
data.getTargetId(), MediaViewController.GUTS_ANIMATION_DURATION + 100L);
});
mController = null;
@@ -752,38 +752,6 @@ public class MediaControlPanel {
return false;
}
/**
* Returns the application info for the media recommendation's source app.
*
* @param target Smartspace target contains a list of media recommendations. Each item should
* contain the same source app's info.
*
* @return The source app's application info. This value can be null if no valid application
* info can be obtained.
*/
private ApplicationInfo getApplicationInfo(@NonNull SmartspaceTarget target) {
List<SmartspaceAction> mediaRecommendationList = target.getIconGrid();
if (mediaRecommendationList == null || mediaRecommendationList.isEmpty()) {
return null;
}
for (SmartspaceAction recommendation: mediaRecommendationList) {
Bundle extras = recommendation.getExtras();
if (extras != null && extras.getString(EXTRAS_MEDIA_SOURCE_PACKAGE_NAME) != null) {
// Get the logo from app's package name when applicable.
String packageName = extras.getString(EXTRAS_MEDIA_SOURCE_PACKAGE_NAME);
try {
return mContext.getPackageManager()
.getApplicationInfo(packageName, 0 /* flags */);
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Fail to get media recommendation's app info", e);
}
}
}
return null;
}
/**
* Get the surface given the current end location for MediaViewController
* @return surface used for Smartspace logging

View File

@@ -16,7 +16,6 @@
package com.android.systemui.media
import android.app.smartspace.SmartspaceTarget
import javax.inject.Inject
/**
@@ -28,7 +27,12 @@ class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener,
private val listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
private val entries: MutableMap<String, Pair<MediaData?, MediaDeviceData?>> = mutableMapOf()
override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
override fun onMediaDataLoaded(
key: String,
oldKey: String?,
data: MediaData,
immediately: Boolean
) {
if (oldKey != null && oldKey != key && entries.contains(oldKey)) {
entries[key] = data to entries.remove(oldKey)?.second
update(key, oldKey)
@@ -40,7 +44,7 @@ class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener,
override fun onSmartspaceMediaDataLoaded(
key: String,
data: SmartspaceTarget,
data: SmartspaceMediaData,
shouldPrioritize: Boolean
) {
listeners.toSet().forEach { it.onSmartspaceMediaDataLoaded(key, data) }
@@ -50,8 +54,8 @@ class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener,
remove(key)
}
override fun onSmartspaceMediaDataRemoved(key: String) {
listeners.toSet().forEach { it.onSmartspaceMediaDataRemoved(key) }
override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
listeners.toSet().forEach { it.onSmartspaceMediaDataRemoved(key, immediately) }
}
override fun onMediaDeviceChanged(

View File

@@ -16,8 +16,6 @@
package com.android.systemui.media
import android.app.smartspace.SmartspaceAction
import android.app.smartspace.SmartspaceTarget
import android.os.SystemProperties
import android.util.Log
import com.android.internal.annotations.VisibleForTesting
@@ -34,6 +32,7 @@ import kotlin.collections.LinkedHashMap
private const val TAG = "MediaDataFilter"
private const val DEBUG = true
private const val RESUMABLE_MEDIA_MAX_AGE_SECONDS_KEY = "resumable_media_max_age_seconds"
/**
* Maximum age of a media control to re-activate on smartspace signal. If there is no media control
@@ -67,8 +66,7 @@ class MediaDataFilter @Inject constructor(
private val allEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
// The filtered userEntries, which will be a subset of all userEntries in MediaDataManager
private val userEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
var hasSmartspace: Boolean = false
private set
private var smartspaceMediaData: SmartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
private var reactivatedKey: String? = null
init {
@@ -81,7 +79,12 @@ class MediaDataFilter @Inject constructor(
userTracker.startTracking()
}
override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
override fun onMediaDataLoaded(
key: String,
oldKey: String?,
data: MediaData,
immediately: Boolean
) {
if (oldKey != null && oldKey != key) {
allEntries.remove(oldKey)
}
@@ -104,18 +107,32 @@ class MediaDataFilter @Inject constructor(
override fun onSmartspaceMediaDataLoaded(
key: String,
data: SmartspaceTarget,
data: SmartspaceMediaData,
shouldPrioritize: Boolean
) {
var shouldPrioritizeMutable = shouldPrioritize
hasSmartspace = true
if (!data.isActive) {
Log.d(TAG, "Inactive recommendation data. Skip triggering.")
return
}
// Override the pass-in value here, as the order of Smartspace card is only determined here.
var shouldPrioritizeMutable = false
smartspaceMediaData = data
// Before forwarding the smartspace target, first check if we have recently inactive media
val sorted = userEntries.toSortedMap(compareBy {
userEntries.get(it)?.lastActive ?: -1
})
val timeSinceActive = timeSinceActiveForMostRecentMedia(sorted)
if (timeSinceActive < SMARTSPACE_MAX_AGE) {
var smartspaceMaxAgeMillis = SMARTSPACE_MAX_AGE
data.cardAction?.let {
val smartspaceMaxAgeSeconds =
it.extras.getLong(RESUMABLE_MEDIA_MAX_AGE_SECONDS_KEY, 0)
if (smartspaceMaxAgeSeconds > 0) {
smartspaceMaxAgeMillis = TimeUnit.SECONDS.toMillis(smartspaceMaxAgeSeconds)
}
}
if (timeSinceActive < smartspaceMaxAgeMillis) {
val lastActiveKey = sorted.lastKey() // most recently active
// Notify listeners to consider this media active
Log.d(TAG, "reactivating $lastActiveKey instead of smartspace")
@@ -129,9 +146,8 @@ class MediaDataFilter @Inject constructor(
shouldPrioritizeMutable = true
}
// Only proceed with the Smartspace update if the recommendation is not empty.
if (isMediaRecommendationEmpty(data)) {
Log.d(TAG, "Empty media recommendations. Skip showing the card")
if (!data.isValid) {
Log.d(TAG, "Invalid recommendation data. Skip showing the rec card")
return
}
listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) }
@@ -147,9 +163,7 @@ class MediaDataFilter @Inject constructor(
}
}
override fun onSmartspaceMediaDataRemoved(key: String) {
hasSmartspace = false
override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
// First check if we had reactivated media instead of forwarding smartspace
reactivatedKey?.let {
val lastActiveKey = it
@@ -158,12 +172,17 @@ class MediaDataFilter @Inject constructor(
// Notify listeners to update with actual active value
userEntries.get(lastActiveKey)?.let { mediaData ->
listeners.forEach {
it.onMediaDataLoaded(lastActiveKey, lastActiveKey, mediaData)
it.onMediaDataLoaded(
lastActiveKey, lastActiveKey, mediaData, immediately)
}
}
}
listeners.forEach { it.onSmartspaceMediaDataRemoved(key) }
if (smartspaceMediaData.isActive) {
smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy(
targetId = smartspaceMediaData.targetId, isValid = smartspaceMediaData.isValid)
}
listeners.forEach { it.onSmartspaceMediaDataRemoved(key, immediately) }
}
@VisibleForTesting
@@ -202,20 +221,22 @@ class MediaDataFilter @Inject constructor(
// Force updates to listeners, needed for re-activated card
mediaDataManager.setTimedOut(it, timedOut = true, forceUpdate = true)
}
if (hasSmartspace) {
mediaDataManager.dismissSmartspaceRecommendation(0L /* delay */)
if (smartspaceMediaData.isActive) {
smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy(
targetId = smartspaceMediaData.targetId, isValid = smartspaceMediaData.isValid)
}
mediaDataManager.dismissSmartspaceRecommendation(smartspaceMediaData.targetId, delay = 0L)
}
/**
* Are there any media notifications active?
*/
fun hasActiveMedia() = userEntries.any { it.value.active } || hasSmartspace
fun hasActiveMedia() = userEntries.any { it.value.active } || smartspaceMediaData.isActive
/**
* Are there any media entries we should display?
*/
fun hasAnyMedia() = userEntries.isNotEmpty() || hasSmartspace
fun hasAnyMedia() = userEntries.isNotEmpty() || smartspaceMediaData.isActive
/**
* Add a listener for filtered [MediaData] changes
@@ -227,12 +248,6 @@ class MediaDataFilter @Inject constructor(
*/
fun removeListener(listener: MediaDataManager.Listener) = _listeners.remove(listener)
/** Check if the Smartspace sends an empty update. */
private fun isMediaRecommendationEmpty(data: SmartspaceTarget): Boolean {
val mediaRecommendationList: List<SmartspaceAction> = data.getIconGrid()
return mediaRecommendationList == null || mediaRecommendationList.isEmpty()
}
/**
* Return the time since last active for the most-recent media.
*

View File

@@ -42,6 +42,7 @@ import android.os.UserHandle
import android.service.notification.StatusBarNotification
import android.text.TextUtils
import android.util.Log
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -76,6 +77,9 @@ private const val DEBUG = true
private val LOADING = MediaData(-1, false, 0, null, null, null, null, null,
emptyList(), emptyList(), "INVALID", null, null, null, true, null)
@VisibleForTesting
internal val EMPTY_SMARTSPACE_MEDIA_DATA = SmartspaceMediaData("INVALID", false, false,
"INVALID", null, emptyList(), 0)
fun isMediaNotification(sbn: StatusBarNotification): Boolean {
if (!sbn.notification.hasMediaSession()) {
@@ -118,6 +122,10 @@ class MediaDataManager(
@JvmField
val SMARTSPACE_UI_SURFACE_LABEL = "media_data_manager"
// Smartspace package name's extra key.
@JvmField
val EXTRAS_MEDIA_SOURCE_PACKAGE_NAME = "package_name"
// Maximum number of actions allowed in compact view
@JvmField
val MAX_COMPACT_ACTIONS = 3
@@ -137,7 +145,7 @@ class MediaDataManager(
private val internalListeners: MutableSet<Listener> = mutableSetOf()
private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
// There should ONLY be at most one Smartspace media recommendation.
private var smartspaceMediaTarget: SmartspaceTarget? = null
private var smartspaceMediaData: SmartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
private var smartspaceSession: SmartspaceSession? = null
@Inject
@@ -360,7 +368,7 @@ class MediaDataManager(
* External listeners registered with [addListener] will be notified after the event propagates
* through the internal listener pipeline.
*/
private fun notifySmartspaceMediaDataLoaded(key: String, info: SmartspaceTarget) {
private fun notifySmartspaceMediaDataLoaded(key: String, info: SmartspaceMediaData) {
internalListeners.forEach { it.onSmartspaceMediaDataLoaded(key, info) }
}
@@ -379,9 +387,13 @@ class MediaDataManager(
*
* External listeners registered with [addListener] will be notified after the event propagates
* through the internal listener pipeline.
*
* @param immediately indicates should apply the UI changes immediately, otherwise wait until
* the next refresh-round before UI becomes visible. Should only be true if the update is
* initiated by user's interaction.
*/
private fun notifySmartspaceMediaDataRemoved(key: String) {
internalListeners.forEach { it.onSmartspaceMediaDataRemoved(key) }
private fun notifySmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
internalListeners.forEach { it.onSmartspaceMediaDataRemoved(key, immediately) }
}
/**
@@ -424,14 +436,18 @@ class MediaDataManager(
* This will make the recommendation view to not be shown anymore during this headphone
* connection session.
*/
fun dismissSmartspaceRecommendation(delay: Long) {
fun dismissSmartspaceRecommendation(key: String, delay: Long) {
Log.d(TAG, "Dismissing Smartspace media target")
// Do not set smartspaceMediaTarget to null. So the instance is preserved during the entire
// headphone connection, and will ONLY be set to null when headphones are disconnected.
smartspaceMediaTarget?.let {
foregroundExecutor.executeDelayed(
{ notifySmartspaceMediaDataRemoved(it.smartspaceTargetId) }, delay)
if (smartspaceMediaData.targetId != key) {
return
}
if (smartspaceMediaData.isActive) {
smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy(
targetId = smartspaceMediaData.targetId)
}
foregroundExecutor.executeDelayed(
{ notifySmartspaceMediaDataRemoved(
smartspaceMediaData.targetId, immediately = true) }, delay)
}
private fun loadMediaDataInBgForResumption(
@@ -680,46 +696,41 @@ class MediaDataManager(
override fun onSmartspaceTargetsUpdated(targets: List<Parcelable>) {
if (!Utils.allowMediaRecommendations(context)) {
Log.d(TAG, "Smartspace recommendation is disabled in Settings.")
return
}
val mediaTargets = targets.filterIsInstance<SmartspaceTarget>()
when (mediaTargets.size) {
0 -> {
Log.d(TAG, "Empty Smartspace media target")
smartspaceMediaTarget?.let {
Log.d(TAG, "Setting Smartspace media target to null")
notifySmartspaceMediaDataRemoved(it.smartspaceTargetId)
if (!smartspaceMediaData.isActive) {
return
}
smartspaceMediaTarget = null
Log.d(TAG, "Set Smartspace media to be inactive for the data update")
smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy(
targetId = smartspaceMediaData.targetId)
notifySmartspaceMediaDataRemoved(smartspaceMediaData.targetId, immediately = false)
}
1 -> {
// TODO(b/182811956): Reactivate the resumable media sessions whose last active
// time is within 3 hours.
// TODO(b/182813365): Wire this up with MediaTimeoutListener so the session can be
// expired after 30 seconds.
val newMediaTarget = mediaTargets.get(0)
if (smartspaceMediaTarget != null &&
smartspaceMediaTarget!!.smartspaceTargetId ==
newMediaTarget.smartspaceTargetId) {
// The same Smartspace updates can be received. Only send the first one.
if (smartspaceMediaData.targetId == newMediaTarget.smartspaceTargetId) {
// The same Smartspace updates can be received. Skip the duplicate updates.
Log.d(TAG, "Same Smartspace media update exists. Skip loading data.")
} else {
smartspaceMediaTarget?.let {
notifySmartspaceMediaDataRemoved(it.smartspaceTargetId)
}
Log.d(TAG, "Forwarding Smartspace media update.")
smartspaceMediaData = toSmartspaceMediaData(newMediaTarget, isActive = true)
notifySmartspaceMediaDataLoaded(
newMediaTarget.smartspaceTargetId, newMediaTarget)
smartspaceMediaTarget = newMediaTarget
smartspaceMediaData.targetId, smartspaceMediaData)
}
}
else -> {
// There should NOT be more than 1 Smartspace media update. When it happens, it
// indicates a bad state or an error. Reset the status accordingly.
Log.wtf(TAG, "More than 1 Smartspace Media Update. Resetting the status...")
smartspaceMediaTarget?.let {
notifySmartspaceMediaDataRemoved(it.smartspaceTargetId)
}
smartspaceMediaTarget = null
notifySmartspaceMediaDataRemoved(
smartspaceMediaData.targetId, false /* immediately */)
smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
}
}
}
@@ -797,28 +808,77 @@ class MediaDataManager(
* oldKey is provided to check whether the view has changed keys, which can happen when a
* player has gone from resume state (key is package name) to active state (key is
* notification key) or vice versa.
*
* @param immediately indicates should apply the UI changes immediately, otherwise wait
* until the next refresh-round before UI becomes visible. True by default to take in place
* immediately.
*/
fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {}
fun onMediaDataLoaded(
key: String,
oldKey: String?,
data: MediaData,
immediately: Boolean = true
) {}
/**
* Called whenever there's new Smartspace media data loaded.
*
* shouldPrioritize indicates the sorting priority of the Smartspace card. If true, it will
* be prioritized as the first card. Otherwise, it will show up as the last card as default.
* @param shouldPrioritize indicates the sorting priority of the Smartspace card. If true,
* it will be prioritized as the first card. Otherwise, it will show up as the last card as
* default.
*/
fun onSmartspaceMediaDataLoaded(
key: String,
data: SmartspaceTarget,
data: SmartspaceMediaData,
shouldPrioritize: Boolean = false
) {}
/**
* Called whenever a previously existing Media notification was removed
*/
/** Called whenever a previously existing Media notification was removed. */
fun onMediaDataRemoved(key: String) {}
/** Called whenever a previously existing Smartspace media data was removed. */
fun onSmartspaceMediaDataRemoved(key: String) {}
/**
* Called whenever a previously existing Smartspace media data was removed.
*
* @param immediately indicates should apply the UI changes immediately, otherwise wait
* until the next refresh-round before UI becomes visible. True by default to take in place
* immediately.
*/
fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean = true) {}
}
/**
* Converts the pass-in SmartspaceTarget to SmartspaceMediaData with the pass-in active status.
*
* @return An empty SmartspaceMediaData with the valid target Id is returned if the
* SmartspaceTarget's data is invalid.
*/
private fun toSmartspaceMediaData(
target: SmartspaceTarget,
isActive: Boolean
): SmartspaceMediaData {
packageName(target)?.let {
return SmartspaceMediaData(target.smartspaceTargetId, isActive, true, it,
target.baseAction, target.iconGrid, 0)
}
return EMPTY_SMARTSPACE_MEDIA_DATA
.copy(targetId = target.smartspaceTargetId, isActive = isActive)
}
private fun packageName(target: SmartspaceTarget): String? {
val recommendationList = target.iconGrid
if (recommendationList == null || recommendationList.isEmpty()) {
Log.d(TAG, "Empty or media recommendation list.")
return null
}
for (recommendation in recommendationList) {
val extras = recommendation.extras
extras?.let {
it.getString(EXTRAS_MEDIA_SOURCE_PACKAGE_NAME)?.let {
packageName -> return packageName }
}
}
Log.d(TAG, "No valid package name is provided.")
return null
}
override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {

View File

@@ -63,7 +63,12 @@ class MediaDeviceManager @Inject constructor(
*/
fun removeListener(listener: Listener) = listeners.remove(listener)
override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
override fun onMediaDataLoaded(
key: String,
oldKey: String?,
data: MediaData,
immediately: Boolean
) {
if (oldKey != null && oldKey != key) {
val oldEntry = entries.remove(oldKey)
oldEntry?.stop()

View File

@@ -1,6 +1,5 @@
package com.android.systemui.media
import android.app.smartspace.SmartspaceTarget
import android.graphics.Rect
import android.util.ArraySet
import android.view.View
@@ -57,13 +56,20 @@ class MediaHost constructor(
}
private val listener = object : MediaDataManager.Listener {
override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
updateViewVisibility()
override fun onMediaDataLoaded(
key: String,
oldKey: String?,
data: MediaData,
immediately: Boolean
) {
if (immediately) {
updateViewVisibility()
}
}
override fun onSmartspaceMediaDataLoaded(
key: String,
data: SmartspaceTarget,
data: SmartspaceMediaData,
shouldPrioritize: Boolean
) {
updateViewVisibility()
@@ -73,8 +79,10 @@ class MediaHost constructor(
updateViewVisibility()
}
override fun onSmartspaceMediaDataRemoved(key: String) {
updateViewVisibility()
override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
if (immediately) {
updateViewVisibility()
}
}
}

View File

@@ -155,7 +155,12 @@ class MediaResumeListener @Inject constructor(
}
}
override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
override fun onMediaDataLoaded(
key: String,
oldKey: String?,
data: MediaData,
immediately: Boolean
) {
if (useMediaResumption) {
// If this had been started from a resume state, disconnect now that it's live
mediaBrowser?.disconnect()

View File

@@ -16,7 +16,6 @@
package com.android.systemui.media
import android.app.smartspace.SmartspaceTarget
import android.content.ComponentName
import android.content.Context
import android.media.session.MediaController
@@ -92,39 +91,44 @@ class MediaSessionBasedFilter @Inject constructor(
* playback type PLAYBACK_TYPE_LOCAL. These updates should be filtered to improve the usability
* of the media controls.
*/
override fun onMediaDataLoaded(key: String, oldKey: String?, info: MediaData) {
override fun onMediaDataLoaded(
key: String,
oldKey: String?,
data: MediaData,
immediately: Boolean
) {
backgroundExecutor.execute {
info.token?.let {
data.token?.let {
tokensWithNotifications.add(it)
}
val isMigration = oldKey != null && key != oldKey
if (isMigration) {
keyedTokens.remove(oldKey)?.let { removed -> keyedTokens.put(key, removed) }
}
if (info.token != null) {
if (data.token != null) {
keyedTokens.get(key)?.let {
tokens ->
tokens.add(info.token)
tokens.add(data.token)
} ?: run {
val tokens = mutableSetOf(info.token)
val tokens = mutableSetOf(data.token)
keyedTokens.put(key, tokens)
}
}
// Determine if an app is casting by checking if it has a session with playback type
// PLAYBACK_TYPE_REMOTE.
val remoteControllers = packageControllers.get(info.packageName)?.filter {
val remoteControllers = packageControllers.get(data.packageName)?.filter {
it.playbackInfo?.playbackType == PlaybackInfo.PLAYBACK_TYPE_REMOTE
}
// Limiting search to only apps with a single remote session.
val remote = if (remoteControllers?.size == 1) remoteControllers.firstOrNull() else null
if (isMigration || remote == null || remote.sessionToken == info.token ||
if (isMigration || remote == null || remote.sessionToken == data.token ||
!tokensWithNotifications.contains(remote.sessionToken)) {
// Not filtering in this case. Passing the event along to listeners.
dispatchMediaDataLoaded(key, oldKey, info)
dispatchMediaDataLoaded(key, oldKey, data, immediately)
} else {
// Filtering this event because the app is casting and the loaded events is for a
// local session.
Log.d(TAG, "filtering key=$key local=${info.token} remote=${remote?.sessionToken}")
Log.d(TAG, "filtering key=$key local=${data.token} remote=${remote?.sessionToken}")
// If the local session uses a different notification key, then lets go a step
// farther and dismiss the media data so that media controls for the local session
// don't hang around while casting.
@@ -137,7 +141,7 @@ class MediaSessionBasedFilter @Inject constructor(
override fun onSmartspaceMediaDataLoaded(
key: String,
data: SmartspaceTarget,
data: SmartspaceMediaData,
shouldPrioritize: Boolean
) {
backgroundExecutor.execute {
@@ -153,15 +157,20 @@ class MediaSessionBasedFilter @Inject constructor(
}
}
override fun onSmartspaceMediaDataRemoved(key: String) {
override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
backgroundExecutor.execute {
dispatchSmartspaceMediaDataRemoved(key)
dispatchSmartspaceMediaDataRemoved(key, immediately)
}
}
private fun dispatchMediaDataLoaded(key: String, oldKey: String?, info: MediaData) {
private fun dispatchMediaDataLoaded(
key: String,
oldKey: String?,
info: MediaData,
immediately: Boolean
) {
foregroundExecutor.execute {
listeners.toSet().forEach { it.onMediaDataLoaded(key, oldKey, info) }
listeners.toSet().forEach { it.onMediaDataLoaded(key, oldKey, info, immediately) }
}
}
@@ -171,15 +180,15 @@ class MediaSessionBasedFilter @Inject constructor(
}
}
private fun dispatchSmartspaceMediaDataLoaded(key: String, info: SmartspaceTarget) {
private fun dispatchSmartspaceMediaDataLoaded(key: String, info: SmartspaceMediaData) {
foregroundExecutor.execute {
listeners.toSet().forEach { it.onSmartspaceMediaDataLoaded(key, info) }
}
}
private fun dispatchSmartspaceMediaDataRemoved(key: String) {
private fun dispatchSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
foregroundExecutor.execute {
listeners.toSet().forEach { it.onSmartspaceMediaDataRemoved(key) }
listeners.toSet().forEach { it.onSmartspaceMediaDataRemoved(key, immediately) }
}
}

View File

@@ -50,7 +50,12 @@ class MediaTimeoutListener @Inject constructor(
*/
lateinit var timeoutCallback: (String, Boolean) -> Unit
override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
override fun onMediaDataLoaded(
key: String,
oldKey: String?,
data: MediaData,
immediately: Boolean
) {
var reusedListener: PlaybackStateListener? = null
// First check if we already have a listener

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.systemui.media
import android.app.smartspace.SmartspaceAction
/** State of a Smartspace media recommendations view. */
data class SmartspaceMediaData(
/**
* Unique id of a Smartspace media target.
*/
val targetId: String,
/**
* Indicates if the status is active.
*/
val isActive: Boolean,
/**
* Indicates if all the required data field is valid.
*/
val isValid: Boolean,
/**
* Package name of the media recommendations' provider-app.
*/
val packageName: String,
/**
* Action to perform when the card is tapped. Also contains the target's extra info.
*/
val cardAction: SmartspaceAction?,
/**
* List of media recommendations.
*/
val recommendations: List<SmartspaceAction>,
/**
* View's background color.
*/
val backgroundColor: Int
)

View File

@@ -24,7 +24,6 @@ import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
import android.app.smartspace.SmartspaceTarget;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
@@ -55,6 +54,7 @@ import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.media.MediaData;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.media.SmartspaceMediaData;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.dagger.StatusBarModule;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
@@ -245,13 +245,12 @@ public class NotificationMediaManager implements Dumpable {
mMediaDataManager.addListener(new MediaDataManager.Listener() {
@Override
public void onMediaDataLoaded(@NonNull String key,
@Nullable String oldKey, @NonNull MediaData data) {
@Nullable String oldKey, @NonNull MediaData data, boolean immediately) {
}
@Override
public void onSmartspaceMediaDataLoaded(@NonNull String key,
@NonNull SmartspaceTarget data, boolean shouldPrioritize) {
@NonNull SmartspaceMediaData data, boolean shouldPrioritize) {
}
@Override
@@ -269,7 +268,7 @@ public class NotificationMediaManager implements Dumpable {
}
@Override
public void onSmartspaceMediaDataRemoved(@NonNull String key) {}
public void onSmartspaceMediaDataRemoved(@NonNull String key, boolean immediately) {}
});
}
@@ -319,12 +318,12 @@ public class NotificationMediaManager implements Dumpable {
mMediaDataManager.addListener(new MediaDataManager.Listener() {
@Override
public void onMediaDataLoaded(@NonNull String key,
@Nullable String oldKey, @NonNull MediaData data) {
@Nullable String oldKey, @NonNull MediaData data, boolean immediately) {
}
@Override
public void onSmartspaceMediaDataLoaded(@NonNull String key,
@NonNull SmartspaceTarget data, boolean shouldPrioritize) {
@NonNull SmartspaceMediaData data, boolean shouldPrioritize) {
}
@@ -341,7 +340,7 @@ public class NotificationMediaManager implements Dumpable {
}
@Override
public void onSmartspaceMediaDataRemoved(@NonNull String key) {}
public void onSmartspaceMediaDataRemoved(@NonNull String key, boolean immediately) {}
});
}

View File

@@ -19,6 +19,7 @@ package com.android.systemui.media;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -81,9 +82,9 @@ public class MediaDataCombineLatestTest extends SysuiTestCase {
@Test
public void eventNotEmittedWithoutDevice() {
// WHEN data source emits an event without device data
mManager.onMediaDataLoaded(KEY, null, mMediaData);
mManager.onMediaDataLoaded(KEY, null, mMediaData, true /* immediately */);
// THEN an event isn't emitted
verify(mListener, never()).onMediaDataLoaded(eq(KEY), any(), any());
verify(mListener, never()).onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean());
}
@Test
@@ -91,7 +92,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase {
// WHEN device source emits an event without media data
mManager.onMediaDeviceChanged(KEY, null, mDeviceData);
// THEN an event isn't emitted
verify(mListener, never()).onMediaDataLoaded(eq(KEY), any(), any());
verify(mListener, never()).onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean());
}
@Test
@@ -99,80 +100,80 @@ public class MediaDataCombineLatestTest extends SysuiTestCase {
// GIVEN that a device event has already been received
mManager.onMediaDeviceChanged(KEY, null, mDeviceData);
// WHEN media event is received
mManager.onMediaDataLoaded(KEY, null, mMediaData);
mManager.onMediaDataLoaded(KEY, null, mMediaData, true /* immediately */);
// THEN the listener receives a combined event
ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
verify(mListener).onMediaDataLoaded(eq(KEY), any(), captor.capture());
verify(mListener).onMediaDataLoaded(eq(KEY), any(), captor.capture(), anyBoolean());
assertThat(captor.getValue().getDevice()).isNotNull();
}
@Test
public void emitEventAfterMediaFirst() {
// GIVEN that media event has already been received
mManager.onMediaDataLoaded(KEY, null, mMediaData);
mManager.onMediaDataLoaded(KEY, null, mMediaData, true /* immediately */);
// WHEN device event is received
mManager.onMediaDeviceChanged(KEY, null, mDeviceData);
// THEN the listener receives a combined event
ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
verify(mListener).onMediaDataLoaded(eq(KEY), any(), captor.capture());
verify(mListener).onMediaDataLoaded(eq(KEY), any(), captor.capture(), anyBoolean());
assertThat(captor.getValue().getDevice()).isNotNull();
}
@Test
public void migrateKeyMediaFirst() {
// GIVEN that media and device info has already been received
mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData);
mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData, true /* immediately */);
mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
reset(mListener);
// WHEN a key migration event is received
mManager.onMediaDataLoaded(KEY, OLD_KEY, mMediaData);
mManager.onMediaDataLoaded(KEY, OLD_KEY, mMediaData, true /* immediately */);
// THEN the listener receives a combined event
ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
verify(mListener).onMediaDataLoaded(eq(KEY), eq(OLD_KEY), captor.capture());
verify(mListener).onMediaDataLoaded(eq(KEY), eq(OLD_KEY), captor.capture(), anyBoolean());
assertThat(captor.getValue().getDevice()).isNotNull();
}
@Test
public void migrateKeyDeviceFirst() {
// GIVEN that media and device info has already been received
mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData);
mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData, true /* immediately */);
mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
reset(mListener);
// WHEN a key migration event is received
mManager.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData);
// THEN the listener receives a combined event
ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
verify(mListener).onMediaDataLoaded(eq(KEY), eq(OLD_KEY), captor.capture());
verify(mListener).onMediaDataLoaded(eq(KEY), eq(OLD_KEY), captor.capture(), anyBoolean());
assertThat(captor.getValue().getDevice()).isNotNull();
}
@Test
public void migrateKeyMediaAfter() {
// GIVEN that media and device info has already been received
mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData);
mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData, true /* immediately */);
mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
mManager.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData);
reset(mListener);
// WHEN a second key migration event is received for media
mManager.onMediaDataLoaded(KEY, OLD_KEY, mMediaData);
mManager.onMediaDataLoaded(KEY, OLD_KEY, mMediaData, true /* immediately */);
// THEN the key has already been migrated
ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
verify(mListener).onMediaDataLoaded(eq(KEY), eq(KEY), captor.capture());
verify(mListener).onMediaDataLoaded(eq(KEY), eq(KEY), captor.capture(), anyBoolean());
assertThat(captor.getValue().getDevice()).isNotNull();
}
@Test
public void migrateKeyDeviceAfter() {
// GIVEN that media and device info has already been received
mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData);
mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData, true /* immediately */);
mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
mManager.onMediaDataLoaded(KEY, OLD_KEY, mMediaData);
mManager.onMediaDataLoaded(KEY, OLD_KEY, mMediaData, true /* immediately */);
reset(mListener);
// WHEN a second key migration event is received for the device
mManager.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData);
// THEN the key has already be migrated
ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
verify(mListener).onMediaDataLoaded(eq(KEY), eq(KEY), captor.capture());
verify(mListener).onMediaDataLoaded(eq(KEY), eq(KEY), captor.capture(), anyBoolean());
assertThat(captor.getValue().getDevice()).isNotNull();
}
@@ -186,7 +187,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase {
@Test
public void mediaDataRemovedAfterMediaEvent() {
mManager.onMediaDataLoaded(KEY, null, mMediaData);
mManager.onMediaDataLoaded(KEY, null, mMediaData, true /* immediately */);
mManager.onMediaDataRemoved(KEY);
verify(mListener).onMediaDataRemoved(eq(KEY));
}
@@ -201,12 +202,13 @@ public class MediaDataCombineLatestTest extends SysuiTestCase {
@Test
public void mediaDataKeyUpdated() {
// GIVEN that device and media events have already been received
mManager.onMediaDataLoaded(KEY, null, mMediaData);
mManager.onMediaDataLoaded(KEY, null, mMediaData, true /* immediately */);
mManager.onMediaDeviceChanged(KEY, null, mDeviceData);
// WHEN the key is changed
mManager.onMediaDataLoaded("NEW_KEY", KEY, mMediaData);
mManager.onMediaDataLoaded("NEW_KEY", KEY, mMediaData, true /* immediately */);
// THEN the listener gets a load event with the correct keys
ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
verify(mListener).onMediaDataLoaded(eq("NEW_KEY"), any(), captor.capture());
verify(mListener).onMediaDataLoaded(
eq("NEW_KEY"), any(), captor.capture(), anyBoolean());
}
}

View File

@@ -17,7 +17,6 @@
package com.android.systemui.media
import android.app.smartspace.SmartspaceAction
import android.app.smartspace.SmartspaceTarget
import android.graphics.Color
import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
@@ -74,7 +73,7 @@ class MediaDataFilterTest : SysuiTestCase() {
@Mock
private lateinit var executor: Executor
@Mock
private lateinit var smartspaceData: SmartspaceTarget
private lateinit var smartspaceData: SmartspaceMediaData
@Mock
private lateinit var smartspaceMediaRecommendationItem: SmartspaceAction
@@ -102,8 +101,11 @@ class MediaDataFilterTest : SysuiTestCase() {
dataGuest = MediaData(USER_GUEST, true, BG_COLOR, APP, null, ARTIST, TITLE, null,
emptyList(), emptyList(), PACKAGE, null, null, device, true, null)
`when`(smartspaceData.smartspaceTargetId).thenReturn(SMARTSPACE_KEY)
`when`(smartspaceData.iconGrid).thenReturn(listOf(smartspaceMediaRecommendationItem))
`when`(smartspaceData.targetId).thenReturn(SMARTSPACE_KEY)
`when`(smartspaceData.isActive).thenReturn(true)
`when`(smartspaceData.isValid).thenReturn(true)
`when`(smartspaceData.packageName).thenReturn(PACKAGE)
`when`(smartspaceData.recommendations).thenReturn(listOf(smartspaceMediaRecommendationItem))
}
private fun setUser(id: Int) {
@@ -118,7 +120,7 @@ class MediaDataFilterTest : SysuiTestCase() {
mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
// THEN we should tell the listener
verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataMain))
verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataMain), eq(true))
}
@Test
@@ -127,7 +129,7 @@ class MediaDataFilterTest : SysuiTestCase() {
mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
// THEN we should NOT tell the listener
verify(listener, never()).onMediaDataLoaded(any(), any(), any())
verify(listener, never()).onMediaDataLoaded(any(), any(), any(), anyBoolean())
}
@Test
@@ -173,10 +175,10 @@ class MediaDataFilterTest : SysuiTestCase() {
setUser(USER_GUEST)
// THEN we should add back the guest user media
verify(listener).onMediaDataLoaded(eq(KEY_ALT), eq(null), eq(dataGuest))
verify(listener).onMediaDataLoaded(eq(KEY_ALT), eq(null), eq(dataGuest), eq(true))
// but not the main user's
verify(listener, never()).onMediaDataLoaded(eq(KEY), any(), eq(dataMain))
verify(listener, never()).onMediaDataLoaded(eq(KEY), any(), eq(dataMain), anyBoolean())
}
@Test
@@ -229,7 +231,7 @@ class MediaDataFilterTest : SysuiTestCase() {
}
@Test
fun testOnSmartspaceMediaDataLoaded_noMedia_nonEmptyRec_prioritizesSmartspace() {
fun testOnSmartspaceMediaDataLoaded_noMedia_activeValidRec_prioritizesSmartspace() {
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
verify(listener)
@@ -238,18 +240,18 @@ class MediaDataFilterTest : SysuiTestCase() {
}
@Test
fun testOnSmartspaceMediaDataLoaded_noMedia_emptyRec_showsNothing() {
`when`(smartspaceData.iconGrid).thenReturn(listOf())
fun testOnSmartspaceMediaDataLoaded_noMedia_inactiveRec_showsNothing() {
`when`(smartspaceData.isActive).thenReturn(false)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
verify(listener, never())
.onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), anyBoolean())
assertThat(mediaDataFilter.hasActiveMedia()).isTrue()
verify(listener, never()).onMediaDataLoaded(any(), any(), any(), anyBoolean())
verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
}
@Test
fun testOnSmartspaceMediaDataLoaded_noRecentMedia_nonEmptyRec_prioritizesSmartspace() {
fun testOnSmartspaceMediaDataLoaded_noRecentMedia_activeValidRec_prioritizesSmartspace() {
val dataOld = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
mediaDataFilter.onMediaDataLoaded(KEY, null, dataOld)
clock.advanceTime(SMARTSPACE_MAX_AGE + 100)
@@ -261,53 +263,68 @@ class MediaDataFilterTest : SysuiTestCase() {
}
@Test
fun testOnSmartspaceMediaDataLoaded_noRecentMedia_emptyRec_showsNothing() {
`when`(smartspaceData.iconGrid).thenReturn(listOf())
fun testOnSmartspaceMediaDataLoaded_noRecentMedia_inactiveRec_showsNothing() {
`when`(smartspaceData.isActive).thenReturn(false)
val dataOld = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
mediaDataFilter.onMediaDataLoaded(KEY, null, dataOld)
clock.advanceTime(SMARTSPACE_MAX_AGE + 100)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
verify(listener, never())
.onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), anyBoolean())
assertThat(mediaDataFilter.hasActiveMedia()).isTrue()
verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
}
@Test
fun testOnSmartspaceMediaDataLoaded_hasRecentMedia_emptyRec_usesMedia() {
`when`(smartspaceData.iconGrid).thenReturn(listOf())
fun testOnSmartspaceMediaDataLoaded_hasRecentMedia_inactiveRec_showsNothing() {
`when`(smartspaceData.isActive).thenReturn(false)
// WHEN we have media that was recently played, but not currently active
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent))
verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true))
// AND we get a smartspace signal
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
// THEN we should tell listeners to treat the media as active instead
verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
}
@Test
fun testOnSmartspaceMediaDataLoaded_hasRecentMedia_activeInvalidRec_usesMedia() {
`when`(smartspaceData.isValid).thenReturn(false)
// WHEN we have media that was recently played, but not currently active
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true))
// AND we get a smartspace signal
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
// THEN we should tell listeners to treat the media as active instead
val dataCurrentAndActive = dataCurrent.copy(active = true)
verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), eq(dataCurrentAndActive))
verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), eq(dataCurrentAndActive), eq(true))
assertThat(mediaDataFilter.hasActiveMedia()).isTrue()
// Smartspace update shouldn't be propagated for the empty rec list.
verify(listener, never())
.onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), anyBoolean())
verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
}
@Test
fun testOnSmartspaceMediaDataLoaded_hasRecentMedia_nonEmptyRec_usesBoth() {
fun testOnSmartspaceMediaDataLoaded_hasRecentMedia_activeValidRec_usesBoth() {
// WHEN we have media that was recently played, but not currently active
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent))
verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true))
// AND we get a smartspace signal
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
// THEN we should tell listeners to treat the media as active instead
val dataCurrentAndActive = dataCurrent.copy(active = true)
verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), eq(dataCurrentAndActive))
verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), eq(dataCurrentAndActive), eq(true))
assertThat(mediaDataFilter.hasActiveMedia()).isTrue()
// Smartspace update should also be propagated but not prioritized.
verify(listener)
@@ -320,7 +337,7 @@ class MediaDataFilterTest : SysuiTestCase() {
mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
assertThat(mediaDataFilter.hasSmartspace).isFalse()
assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
}
@Test
@@ -331,9 +348,8 @@ class MediaDataFilterTest : SysuiTestCase() {
mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), eq(dataCurrent))
verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), eq(dataCurrent), eq(true))
verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
assertThat(mediaDataFilter.hasSmartspace).isFalse()
}
}

View File

@@ -2,6 +2,7 @@ package com.android.systemui.media
import android.app.Notification.MediaStyle
import android.app.PendingIntent
import android.app.smartspace.SmartspaceAction
import android.app.smartspace.SmartspaceTarget
import android.graphics.Bitmap
import android.media.MediaDescription
@@ -9,6 +10,7 @@ import android.media.MediaMetadata
import android.media.session.MediaController
import android.media.session.MediaSession
import android.provider.Settings
import android.os.Bundle
import android.service.notification.StatusBarNotification
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
@@ -79,6 +81,7 @@ class MediaDataManagerTest : SysuiTestCase() {
@Mock lateinit var activityStarter: ActivityStarter
lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider
@Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget
@Mock private lateinit var mediaRecommendationItem: SmartspaceAction
lateinit var mediaDataManager: MediaDataManager
lateinit var mediaNotification: StatusBarNotification
@Captor lateinit var mediaDataCaptor: ArgumentCaptor<MediaData>
@@ -137,8 +140,12 @@ class MediaDataManagerTest : SysuiTestCase() {
// treat mediaSessionBasedFilter as a listener for testing.
listener = mediaSessionBasedFilter
val recommendationExtras = Bundle()
recommendationExtras.putString("package_name", PACKAGE_NAME)
whenever(mediaRecommendationItem.extras).thenReturn(recommendationExtras)
whenever(mediaSmartspaceTarget.smartspaceTargetId).thenReturn(KEY_MEDIA_SMARTSPACE)
whenever(mediaSmartspaceTarget.featureType).thenReturn(SmartspaceTarget.FEATURE_MEDIA)
whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf(mediaRecommendationItem))
}
@After
@@ -172,7 +179,7 @@ class MediaDataManagerTest : SysuiTestCase() {
fun testOnMetaDataLoaded_callsListener() {
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java))
verify(listener).onMediaDataLoaded(eq(KEY), eq(null), anyObject())
verify(listener).onMediaDataLoaded(eq(KEY), eq(null), anyObject(), eq(true))
}
@Test
@@ -183,7 +190,7 @@ class MediaDataManagerTest : SysuiTestCase() {
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor))
verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true))
assertThat(mediaDataCaptor.value!!.active).isTrue()
}
@@ -202,14 +209,15 @@ class MediaDataManagerTest : SysuiTestCase() {
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor))
verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true))
val data = mediaDataCaptor.value
assertThat(data.resumption).isFalse()
mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
// WHEN the notification is removed
mediaDataManager.onNotificationRemoved(KEY)
// THEN the media data indicates that it is for resumption
verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor))
verify(listener)
.onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true))
assertThat(mediaDataCaptor.value.resumption).isTrue()
}
@@ -221,7 +229,8 @@ class MediaDataManagerTest : SysuiTestCase() {
mediaDataManager.onNotificationAdded(KEY_2, mediaNotification)
assertThat(backgroundExecutor.runAllReady()).isEqualTo(2)
assertThat(foregroundExecutor.runAllReady()).isEqualTo(2)
verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor))
verify(listener)
.onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true))
val data = mediaDataCaptor.value
assertThat(data.resumption).isFalse()
val resumableData = data.copy(resumeAction = Runnable {})
@@ -231,14 +240,16 @@ class MediaDataManagerTest : SysuiTestCase() {
// WHEN the first is removed
mediaDataManager.onNotificationRemoved(KEY)
// THEN the data is for resumption and the key is migrated to the package name
verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor))
verify(listener)
.onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true))
assertThat(mediaDataCaptor.value.resumption).isTrue()
verify(listener, never()).onMediaDataRemoved(eq(KEY))
// WHEN the second is removed
mediaDataManager.onNotificationRemoved(KEY_2)
// THEN the data is for resumption and the second key is removed
verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(PACKAGE_NAME),
capture(mediaDataCaptor))
verify(listener)
.onMediaDataLoaded(
eq(PACKAGE_NAME), eq(PACKAGE_NAME), capture(mediaDataCaptor), eq(true))
assertThat(mediaDataCaptor.value.resumption).isTrue()
verify(listener).onMediaDataRemoved(eq(KEY_2))
}
@@ -252,7 +263,7 @@ class MediaDataManagerTest : SysuiTestCase() {
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor))
verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true))
val data = mediaDataCaptor.value
val dataRemoteWithResume = data.copy(resumeAction = Runnable {}, isLocalSession = false)
mediaDataManager.onMediaDataLoaded(KEY, null, dataRemoteWithResume)
@@ -277,7 +288,8 @@ class MediaDataManagerTest : SysuiTestCase() {
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
// THEN the media data indicates that it is for resumption
verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(null), capture(mediaDataCaptor))
verify(listener)
.onMediaDataLoaded(eq(PACKAGE_NAME), eq(null), capture(mediaDataCaptor), eq(true))
val data = mediaDataCaptor.value
assertThat(data.resumption).isTrue()
assertThat(data.song).isEqualTo(SESSION_TITLE)
@@ -316,14 +328,29 @@ class MediaDataManagerTest : SysuiTestCase() {
// THEN it still loads
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor))
verify(listener)
.onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true))
}
@Test
fun testOnSmartspaceMediaDataLoaded_hasNewMediaTarget_callsListener() {
fun testOnSmartspaceMediaDataLoaded_hasNewValidMediaTarget_callsListener() {
smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
verify(listener).onSmartspaceMediaDataLoaded(
eq(KEY_MEDIA_SMARTSPACE), eq(mediaSmartspaceTarget), eq(false))
eq(KEY_MEDIA_SMARTSPACE),
eq(SmartspaceMediaData(KEY_MEDIA_SMARTSPACE, true /* isActive */, true /*isValid */,
PACKAGE_NAME, null, listOf(mediaRecommendationItem), 0)),
eq(false))
}
@Test
fun testOnSmartspaceMediaDataLoaded_hasNewInvalidMediaTarget_callsListener() {
whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf())
smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
verify(listener).onSmartspaceMediaDataLoaded(
eq(KEY_MEDIA_SMARTSPACE),
eq(EMPTY_SMARTSPACE_MEDIA_DATA
.copy(targetId = KEY_MEDIA_SMARTSPACE, isActive = true, isValid = false)),
eq(false))
}
@Test
@@ -337,7 +364,7 @@ class MediaDataManagerTest : SysuiTestCase() {
fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_callsRemoveListener() {
smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
smartspaceMediaDataProvider.onTargetsAvailable(listOf())
verify(listener).onSmartspaceMediaDataRemoved(KEY_MEDIA_SMARTSPACE)
verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false))
}
@Test
@@ -358,7 +385,7 @@ class MediaDataManagerTest : SysuiTestCase() {
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor))
verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true))
assertThat(mediaDataCaptor.value!!.lastActive).isAtLeast(currentTime)
}
@@ -375,7 +402,7 @@ class MediaDataManagerTest : SysuiTestCase() {
mediaDataManager.setTimedOut(KEY, true, true)
// THEN the last active time is not changed
verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), capture(mediaDataCaptor))
verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), capture(mediaDataCaptor), eq(true))
assertThat(mediaDataCaptor.value.lastActive).isLessThan(currentTime)
}
@@ -386,7 +413,7 @@ class MediaDataManagerTest : SysuiTestCase() {
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor))
verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true))
val data = mediaDataCaptor.value
assertThat(data.resumption).isFalse()
mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
@@ -397,7 +424,8 @@ class MediaDataManagerTest : SysuiTestCase() {
mediaDataManager.onNotificationRemoved(KEY)
// THEN the last active time is not changed
verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor))
verify(listener)
.onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true))
assertThat(mediaDataCaptor.value.resumption).isTrue()
assertThat(mediaDataCaptor.value.lastActive).isLessThan(currentTime)
}
@@ -423,7 +451,7 @@ class MediaDataManagerTest : SysuiTestCase() {
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
// THEN only the first MAX_COMPACT_ACTIONS are actually set
verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor))
verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true))
assertThat(mediaDataCaptor.value.actionsToShowInCompact.size).isEqualTo(
MediaDataManager.MAX_COMPACT_ACTIONS)
}

View File

@@ -36,6 +36,7 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.any
@@ -184,7 +185,7 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
filter.onMediaDataLoaded(KEY, null, mediaData1)
bgExecutor.runAllReady()
fgExecutor.runAllReady()
verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1))
verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true))
}
@Test
@@ -206,7 +207,7 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
bgExecutor.runAllReady()
fgExecutor.runAllReady()
// THEN the event is not filtered
verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1))
verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true))
}
@Test
@@ -235,7 +236,7 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
bgExecutor.runAllReady()
fgExecutor.runAllReady()
// THEN the event is not filtered
verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1))
verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true))
}
@Test
@@ -250,13 +251,14 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
bgExecutor.runAllReady()
fgExecutor.runAllReady()
// THEN the event is not filtered
verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1))
verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true))
// WHEN a loaded event is received that matches the local session
filter.onMediaDataLoaded(KEY, null, mediaData2)
bgExecutor.runAllReady()
fgExecutor.runAllReady()
// THEN the event is filtered
verify(mediaListener, never()).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData2))
verify(mediaListener, never()).onMediaDataLoaded(
eq(KEY), eq(null), eq(mediaData2), anyBoolean())
}
@Test
@@ -272,7 +274,7 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
fgExecutor.runAllReady()
// THEN the event is not filtered because there isn't a notification for the remote
// session.
verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1))
verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true))
}
@Test
@@ -289,13 +291,14 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
bgExecutor.runAllReady()
fgExecutor.runAllReady()
// THEN the event is not filtered
verify(mediaListener).onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1))
verify(mediaListener).onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1), eq(true))
// WHEN a loaded event is received that matches the local session
filter.onMediaDataLoaded(key2, null, mediaData2)
bgExecutor.runAllReady()
fgExecutor.runAllReady()
// THEN the event is filtered
verify(mediaListener, never()).onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2))
verify(mediaListener, never())
.onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), anyBoolean())
// AND there should be a removed event for key2
verify(mediaListener).onMediaDataRemoved(eq(key2))
}
@@ -314,13 +317,13 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
bgExecutor.runAllReady()
fgExecutor.runAllReady()
// THEN the event is not filtered
verify(mediaListener).onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1))
verify(mediaListener).onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1), eq(true))
// WHEN a loaded event is received that matches the remote session
filter.onMediaDataLoaded(key2, null, mediaData2)
bgExecutor.runAllReady()
fgExecutor.runAllReady()
// THEN the event is not filtered
verify(mediaListener).onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2))
verify(mediaListener).onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), eq(true))
}
@Test
@@ -336,13 +339,13 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
bgExecutor.runAllReady()
fgExecutor.runAllReady()
// THEN the event is not filtered
verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1))
verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true))
// WHEN a loaded event is received that matches the local session
filter.onMediaDataLoaded(KEY, null, mediaData2)
bgExecutor.runAllReady()
fgExecutor.runAllReady()
// THEN the event is not filtered
verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData2))
verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData2), eq(true))
}
@Test
@@ -360,7 +363,7 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
bgExecutor.runAllReady()
fgExecutor.runAllReady()
// THEN the event is not filtered
verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1))
verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true))
}
@Test
@@ -382,7 +385,7 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
bgExecutor.runAllReady()
fgExecutor.runAllReady()
// THEN the key migration event is fired
verify(mediaListener).onMediaDataLoaded(eq(key2), eq(key1), eq(mediaData2))
verify(mediaListener).onMediaDataLoaded(eq(key2), eq(key1), eq(mediaData2), eq(true))
}
@Test
@@ -411,12 +414,13 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
bgExecutor.runAllReady()
fgExecutor.runAllReady()
// THEN the key migration event is filtered
verify(mediaListener, never()).onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2))
verify(mediaListener, never())
.onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), anyBoolean())
// WHEN a loaded event is received that matches the remote session
filter.onMediaDataLoaded(key2, null, mediaData1)
bgExecutor.runAllReady()
fgExecutor.runAllReady()
// THEN the key migration event is fired
verify(mediaListener).onMediaDataLoaded(eq(key2), eq(null), eq(mediaData1))
verify(mediaListener).onMediaDataLoaded(eq(key2), eq(null), eq(mediaData1), eq(true))
}
}