Merge "On Smartspace removal update, only dismiss media recommendation/player when it's invisible to users." into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
a0ecdaafd5
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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>) {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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) {}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user