Merge "On Smartspace removal update, only dismiss media recommendation/player when it's invisible to users." into sc-dev
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index a1c06fc..09da9d2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -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 @@
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 @@
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 @@
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 @@
// 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 @@
private fun addSmartspaceMediaRecommendations(
key: String,
- data: SmartspaceTarget,
+ data: SmartspaceMediaData,
shouldPrioritize: Boolean
) {
Log.d(TAG, "Updating smartspace target in carousel")
@@ -342,6 +354,11 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
index c806bcf..45ceceb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
@@ -560,12 +560,10 @@
}
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)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 27a4e93..55feea9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -20,7 +20,6 @@
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.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 @@
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 @@
};
}
- /** 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 @@
}
// 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 @@
/* isRecommendationCard */ true);
closeGuts();
mMediaDataManagerLazy.get().dismissSmartspaceRecommendation(
- MediaViewController.GUTS_ANIMATION_DURATION + 100L);
+ data.getTargetId(), MediaViewController.GUTS_ANIMATION_DURATION + 100L);
});
mController = null;
@@ -753,38 +753,6 @@
}
/**
- * 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
*/
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
index 87af9e0..ee1d3ea 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
@@ -16,7 +16,6 @@
package com.android.systemui.media
-import android.app.smartspace.SmartspaceTarget
import javax.inject.Inject
/**
@@ -28,7 +27,12 @@
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 @@
override fun onSmartspaceMediaDataLoaded(
key: String,
- data: SmartspaceTarget,
+ data: SmartspaceMediaData,
shouldPrioritize: Boolean
) {
listeners.toSet().forEach { it.onSmartspaceMediaDataLoaded(key, data) }
@@ -50,8 +54,8 @@
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(
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
index 3deb5d1..a611b60 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
@@ -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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
}
- 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 @@
// 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 @@
// 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 @@
*/
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.
*
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index c74f2fe..13c7f71 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -42,6 +42,7 @@
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 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 @@
@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 @@
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 @@
* 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 @@
*
* 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 @@
* 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 @@
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 @@
* 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>) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
index a993d00..52ecbea 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
@@ -63,7 +63,12 @@
*/
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()
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
index fe20dcb..43e2142 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
@@ -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 @@
}
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 @@
updateViewVisibility()
}
- override fun onSmartspaceMediaDataRemoved(key: String) {
- updateViewVisibility()
+ override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
+ if (immediately) {
+ updateViewVisibility()
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
index 7fe408f..9aeb63d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
@@ -155,7 +155,12 @@
}
}
- 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()
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt
index 3e5e8248..a4f33e3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt
@@ -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 @@
* 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 @@
override fun onSmartspaceMediaDataLoaded(
key: String,
- data: SmartspaceTarget,
+ data: SmartspaceMediaData,
shouldPrioritize: Boolean
) {
backgroundExecutor.execute {
@@ -153,15 +157,20 @@
}
}
- 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 @@
}
}
- 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) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
index 8bfe94b..bbea140 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
@@ -50,7 +50,12 @@
*/
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
diff --git a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt
new file mode 100644
index 0000000..9ac1289
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt
@@ -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
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index f99436f..a69b8d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -24,7 +24,6 @@
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.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 @@
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 @@
}
@Override
- public void onSmartspaceMediaDataRemoved(@NonNull String key) {}
+ public void onSmartspaceMediaDataRemoved(@NonNull String key, boolean immediately) {}
});
}
@@ -319,12 +318,12 @@
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 @@
}
@Override
- public void onSmartspaceMediaDataRemoved(@NonNull String key) {}
+ public void onSmartspaceMediaDataRemoved(@NonNull String key, boolean immediately) {}
});
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
index 4a487be..e20b426 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
@@ -19,6 +19,7 @@
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 @@
@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 @@
// 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 @@
// 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 @@
@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 @@
@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());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
index fc0506a..17f2a07 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
@@ -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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
}
@Test
- fun testOnSmartspaceMediaDataLoaded_noMedia_nonEmptyRec_prioritizesSmartspace() {
+ fun testOnSmartspaceMediaDataLoaded_noMedia_activeValidRec_prioritizesSmartspace() {
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
verify(listener)
@@ -238,18 +240,18 @@
}
@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 @@
}
@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 @@
mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
- assertThat(mediaDataFilter.hasSmartspace).isFalse()
+ assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
}
@Test
@@ -331,9 +348,8 @@
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()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index dfb149d..15cfee8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -2,6 +2,7 @@
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.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 @@
@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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt
index 2d90cc4..c6d7e92 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt
@@ -36,6 +36,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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))
}
}