diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt index d54b1514abf70..b85f107260403 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt @@ -37,7 +37,7 @@ import javax.inject.Provider private const val TAG = "MediaCarouselController" private val settingsIntent = Intent().setAction(ACTION_MEDIA_CONTROLS_SETTINGS) -private const val DEBUG = false +private val DEBUG = Log.isLoggable(TAG, Log.DEBUG) /** * Class that is responsible for keeping the view carousel up to date. @@ -209,41 +209,56 @@ class MediaCarouselController @Inject constructor( oldKey: String?, data: MediaData, immediately: Boolean, - isSsReactivated: Boolean + receivedSmartspaceCardLatency: Int ) { if (addOrUpdatePlayer(key, oldKey, data)) { // Log card received if a new resumable media card is added MediaPlayerData.getMediaPlayer(key)?.let { + /* ktlint-disable max-line-length */ logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED it.mInstanceId, it.mUid, /* isRecommendationCard */ false, - it.surfaceForSmartspaceLogging, + intArrayOf( + SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE, + SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN), rank = MediaPlayerData.getMediaPlayerIndex(key)) + /* ktlint-disable max-line-length */ } - } - if (isSsReactivated) { - // If resumable media is reactivated by headphone connection, update instance - // id for each card and log a receive event. + if (mediaCarouselScrollHandler.visibleToUser && + mediaCarouselScrollHandler.visibleMediaIndex + == MediaPlayerData.getMediaPlayerIndex(key)) { + logSmartspaceImpression(mediaCarouselScrollHandler.qsExpanded) + } + } else if (receivedSmartspaceCardLatency != 0) { + // Log resume card received if resumable media card is reactivated and + // resume card is ranked first MediaPlayerData.players().forEachIndexed { index, it -> if (it.recommendationViewHolder == null) { it.mInstanceId = SmallHash.hash(it.mUid + systemClock.currentTimeMillis().toInt()) + it.mIsImpressed = false + /* ktlint-disable max-line-length */ logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED it.mInstanceId, it.mUid, /* isRecommendationCard */ false, - it.surfaceForSmartspaceLogging, - rank = index) + intArrayOf( + SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE, + SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN), + rank = index, + receivedLatencyMillis = receivedSmartspaceCardLatency) + /* ktlint-disable max-line-length */ } } + // If media container area already visible to the user, log impression for + // reactivated card. + if (mediaCarouselScrollHandler.visibleToUser && + !mediaCarouselScrollHandler.qsExpanded) { + logSmartspaceImpression(mediaCarouselScrollHandler.qsExpanded) + } } - if (mediaCarouselScrollHandler.visibleToUser && - isSsReactivated && !mediaCarouselScrollHandler.qsExpanded) { - // It could happen that reactived media player isn't visible to user because - // of it is a resumption card. - logSmartspaceImpression(mediaCarouselScrollHandler.qsExpanded) - } + val canRemove = data.isPlaying?.let { !it } ?: data.isClearable && !data.active if (canRemove && !Utils.useMediaResumption(context)) { // This view isn't playing, let's remove this! This happens e.g when @@ -262,28 +277,51 @@ class MediaCarouselController @Inject constructor( override fun onSmartspaceMediaDataLoaded( key: String, data: SmartspaceMediaData, - shouldPrioritize: Boolean + shouldPrioritize: Boolean, + isSsReactivated: Boolean ) { if (DEBUG) Log.d(TAG, "Loading Smartspace media update") if (data.isActive) { + if (isSsReactivated && shouldPrioritize) { + // Log resume card received if resumable media card is reactivated and + // recommendation card is valid and ranked first + MediaPlayerData.players().forEachIndexed { index, it -> + if (it.recommendationViewHolder == null) { + it.mInstanceId = SmallHash.hash(it.mUid + + systemClock.currentTimeMillis().toInt()) + it.mIsImpressed = false + /* ktlint-disable max-line-length */ + logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED + it.mInstanceId, + it.mUid, + /* isRecommendationCard */ false, + intArrayOf( + SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE, + SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN), + rank = index, + receivedLatencyMillis = (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis).toInt()) + /* ktlint-disable max-line-length */ + } + } + } addSmartspaceMediaRecommendations(key, data, shouldPrioritize) MediaPlayerData.getMediaPlayer(key)?.let { + /* ktlint-disable max-line-length */ logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED it.mInstanceId, it.mUid, /* isRecommendationCard */ true, - it.surfaceForSmartspaceLogging, - rank = MediaPlayerData.getMediaPlayerIndex(key)) - - if (mediaCarouselScrollHandler.visibleToUser && - mediaCarouselScrollHandler.visibleMediaIndex == - MediaPlayerData.getMediaPlayerIndex(key)) { - logSmartspaceCardReported(800, // SMARTSPACE_CARD_SEEN - it.mInstanceId, - it.mUid, - /* isRecommendationCard */ true, - it.surfaceForSmartspaceLogging) - } + intArrayOf( + SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE, + SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN), + rank = MediaPlayerData.getMediaPlayerIndex(key), + receivedLatencyMillis = (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis).toInt()) + /* ktlint-disable max-line-length */ + } + if (mediaCarouselScrollHandler.visibleToUser && + mediaCarouselScrollHandler.visibleMediaIndex + == MediaPlayerData.getMediaPlayerIndex(key)) { + logSmartspaceImpression(mediaCarouselScrollHandler.qsExpanded) } } else { onSmartspaceMediaDataRemoved(data.targetId, immediately = true) @@ -718,7 +756,8 @@ class MediaCarouselController @Inject constructor( mediaControlPanel.mInstanceId, mediaControlPanel.mUid, isRecommendationCard, - mediaControlPanel.surfaceForSmartspaceLogging) + intArrayOf(mediaControlPanel.surfaceForSmartspaceLogging)) + mediaControlPanel.mIsImpressed = true } } @@ -731,12 +770,15 @@ class MediaCarouselController @Inject constructor( * instanceId * @param uid uid for the application that media comes from * @param isRecommendationCard whether the card is media recommendation - * @param surface which display surface the media card is on (e.g. lockscreen, shade) + * @param surfaces list of display surfaces the media card is on (e.g. lockscreen, shade) when + * the event happened * @param interactedSubcardRank the rank for interacted media item for recommendation card, -1 * for tapping on card but not on any media item, 0 for first media item, 1 for second, etc. * @param interactedSubcardCardinality how many media items were shown to the user when there * is user interaction * @param rank the rank for media card in the media carousel, starting from 0 + * @param receivedLatencyMillis latency in milliseconds for card received events. E.g. latency + * between headphone connection to sysUI displays media recommendation card * */ fun logSmartspaceCardReported( @@ -744,10 +786,11 @@ class MediaCarouselController @Inject constructor( instanceId: Int, uid: Int, isRecommendationCard: Boolean, - surface: Int, + surfaces: IntArray, interactedSubcardRank: Int = 0, interactedSubcardCardinality: Int = 0, - rank: Int = mediaCarouselScrollHandler.visibleMediaIndex + rank: Int = mediaCarouselScrollHandler.visibleMediaIndex, + receivedLatencyMillis: Int = 0 ) { // Only log media resume card when Smartspace data is available if (!isRecommendationCard && @@ -756,50 +799,52 @@ class MediaCarouselController @Inject constructor( return } - /* ktlint-disable max-line-length */ - SysUiStatsLog.write(SysUiStatsLog.SMARTSPACE_CARD_REPORTED, - eventId, - instanceId, - // Deprecated, replaced with AiAi feature type so we don't need to create logging - // card type for each new feature. - SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD, - surface, - rank, - mediaContent.getChildCount(), - if (isRecommendationCard) - 15 // MEDIA_RECOMMENDATION - else - 31, // MEDIA_RESUME - uid, - interactedSubcardRank, - interactedSubcardCardinality, - 0 // received_latency_millis - ) - /* ktlint-disable max-line-length */ + val cardinality = mediaContent.getChildCount() + surfaces.forEach { surface -> + /* ktlint-disable max-line-length */ + SysUiStatsLog.write(SysUiStatsLog.SMARTSPACE_CARD_REPORTED, + eventId, + instanceId, + // Deprecated, replaced with AiAi feature type so we don't need to create logging + // card type for each new feature. + SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD, + surface, + rank, + cardinality, + if (isRecommendationCard) + 15 // MEDIA_RECOMMENDATION + else + 31, // MEDIA_RESUME + uid, + interactedSubcardRank, + interactedSubcardCardinality, + receivedLatencyMillis + ) + /* ktlint-disable max-line-length */ + if (DEBUG) { + Log.d(TAG, "Log Smartspace card event id: $eventId instance id: $instanceId" + + " surface: $surface rank: $rank cardinality: $cardinality " + + "isRecommendationCard: $isRecommendationCard uid: $uid " + + "interactedSubcardRank: $interactedSubcardRank " + + "interactedSubcardCardinality: $interactedSubcardCardinality " + + "received_latency_millis: $receivedLatencyMillis") + } + } } private fun onSwipeToDismiss() { - val recommendation = MediaPlayerData.players().filter { - it.recommendationViewHolder != null - } - // Use -1 as rank value to indicate user swipe to dismiss the card - if (!recommendation.isEmpty()) { - logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS - recommendation.get(0).mInstanceId, - recommendation.get(0).mUid, - true, - recommendation.get(0).surfaceForSmartspaceLogging, - rank = -1) - } else { - val visibleMediaIndex = mediaCarouselScrollHandler.visibleMediaIndex - if (MediaPlayerData.players().size > visibleMediaIndex) { - val player = MediaPlayerData.players().elementAt(visibleMediaIndex) + MediaPlayerData.players().forEachIndexed { + index, it -> + if (it.mIsImpressed) { logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS - player.mInstanceId, - player.mUid, - false, - player.surfaceForSmartspaceLogging, + it.mInstanceId, + it.mUid, + it.recommendationViewHolder != null, + intArrayOf(it.surfaceForSmartspaceLogging), + // Use -1 as rank value to indicate user swipe to dismiss the card rank = -1) + // Reset card impressed state when swipe to dismissed + it.mIsImpressed = false } } mediaManager.onSwipeToDismiss() diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index e73eb66a51bfe..f66eb5ba008a4 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -59,6 +59,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.phone.KeyguardDismissUtil; import com.android.systemui.util.animation.TransitionLayout; +import com.android.systemui.util.time.SystemClock; import java.net.URISyntaxException; import java.util.List; @@ -121,6 +122,9 @@ public class MediaControlPanel { private MediaCarouselController mMediaCarouselController; private final MediaOutputDialogFactory mMediaOutputDialogFactory; private final FalsingManager mFalsingManager; + // Used for swipe-to-dismiss logging. + protected boolean mIsImpressed = false; + private SystemClock mSystemClock; /** * Initialize a new control panel @@ -134,7 +138,7 @@ public class MediaControlPanel { SeekBarViewModel seekBarViewModel, Lazy lazyMediaDataManager, KeyguardDismissUtil keyguardDismissUtil, MediaOutputDialogFactory mediaOutputDialogFactory, MediaCarouselController mediaCarouselController, - FalsingManager falsingManager) { + FalsingManager falsingManager, SystemClock systemClock) { mContext = context; mBackgroundExecutor = backgroundExecutor; mActivityStarter = activityStarter; @@ -145,6 +149,8 @@ public class MediaControlPanel { mMediaOutputDialogFactory = mediaOutputDialogFactory; mMediaCarouselController = mediaCarouselController; mFalsingManager = falsingManager; + mSystemClock = systemClock; + loadDimens(); mSeekBarViewModel.setLogSmartspaceClick(() -> { @@ -291,7 +297,10 @@ public class MediaControlPanel { } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "Unable to look up package name", e); } - mInstanceId = SmallHash.hash(mUid); + // Only assigns instance id if it's unassigned. + if (mInstanceId == -1) { + mInstanceId = SmallHash.hash(mUid + (int) mSystemClock.currentTimeMillis()); + } mBackgroundColor = data.getBackgroundColor(); if (mToken == null || !mToken.equals(token)) { @@ -885,7 +894,7 @@ public class MediaControlPanel { mInstanceId, mUid, isRecommendationCard, - getSurfaceForSmartspaceLogging(), + new int[]{getSurfaceForSmartspaceLogging()}, interactedSubcardRank, interactedSubcardCardinality); } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt index 296bfda89432b..b68f2a7654f0f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt @@ -32,7 +32,7 @@ class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener, oldKey: String?, data: MediaData, immediately: Boolean, - isSsReactivated: Boolean + receivedSmartspaceCardLatency: Int ) { if (oldKey != null && oldKey != key && entries.contains(oldKey)) { entries[key] = data to entries.remove(oldKey)?.second @@ -46,7 +46,8 @@ class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener, override fun onSmartspaceMediaDataLoaded( key: String, data: SmartspaceMediaData, - shouldPrioritize: Boolean + shouldPrioritize: Boolean, + isSsReactivated: Boolean ) { listeners.toSet().forEach { it.onSmartspaceMediaDataLoaded(key, data) } } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt index 79206e86da092..ae5c1f2b19a98 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt @@ -56,7 +56,6 @@ internal val SMARTSPACE_MAX_AGE = SystemProperties class MediaDataFilter @Inject constructor( private val context: Context, private val broadcastDispatcher: BroadcastDispatcher, - private val mediaResumeListener: MediaResumeListener, private val lockscreenUserManager: NotificationLockscreenUserManager, @Main private val executor: Executor, private val systemClock: SystemClock @@ -88,7 +87,7 @@ class MediaDataFilter @Inject constructor( oldKey: String?, data: MediaData, immediately: Boolean, - isSsReactivated: Boolean + receivedSmartspaceCardLatency: Int ) { if (oldKey != null && oldKey != key) { allEntries.remove(oldKey) @@ -106,14 +105,15 @@ class MediaDataFilter @Inject constructor( // Notify listeners listeners.forEach { - it.onMediaDataLoaded(key, oldKey, data, isSsReactivated = isSsReactivated) + it.onMediaDataLoaded(key, oldKey, data) } } override fun onSmartspaceMediaDataLoaded( key: String, data: SmartspaceMediaData, - shouldPrioritize: Boolean + shouldPrioritize: Boolean, + isSsReactivated: Boolean ) { if (!data.isActive) { Log.d(TAG, "Inactive recommendation data. Skip triggering.") @@ -123,8 +123,6 @@ class MediaDataFilter @Inject constructor( // Override the pass-in value here, as the order of Smartspace card is only determined here. var shouldPrioritizeMutable = false smartspaceMediaData = data - // Override the pass-in value here, as the Smartspace reactivation could only happen here. - var isSsReactivated = false // Before forwarding the smartspace target, first check if we have recently inactive media val sorted = userEntries.toSortedMap(compareBy { @@ -139,18 +137,25 @@ class MediaDataFilter @Inject constructor( smartspaceMaxAgeMillis = TimeUnit.SECONDS.toMillis(smartspaceMaxAgeSeconds) } } + + val activeMedia = userEntries.filter { (key, value) -> value.active } + var isSsReactivatedMutable = activeMedia.isEmpty() && userEntries.isNotEmpty() + 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") - reactivatedKey = lastActiveKey - if (MediaPlayerData.firstActiveMediaIndex() == -1) { - isSsReactivated = true - } - val mediaData = sorted.get(lastActiveKey)!!.copy(active = true) - listeners.forEach { - it.onMediaDataLoaded(lastActiveKey, lastActiveKey, mediaData, - isSsReactivated = isSsReactivated) + // It could happen there are existing active media resume cards, then we don't need to + // reactivate. + if (isSsReactivatedMutable) { + val lastActiveKey = sorted.lastKey() // most recently active + // Notify listeners to consider this media active + Log.d(TAG, "reactivating $lastActiveKey instead of smartspace") + reactivatedKey = lastActiveKey + val mediaData = sorted.get(lastActiveKey)!!.copy(active = true) + listeners.forEach { + it.onMediaDataLoaded(lastActiveKey, lastActiveKey, mediaData, + receivedSmartspaceCardLatency = + (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis) + .toInt()) + } } } else { // Mark to prioritize Smartspace card if no recent media. @@ -161,7 +166,8 @@ class MediaDataFilter @Inject constructor( Log.d(TAG, "Invalid recommendation data. Skip showing the rec card") return } - listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) } + listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable, + isSsReactivatedMutable) } } override fun onMediaDataRemoved(key: String) { diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index 6e86bef40181b..7c0f7fc2967e4 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -84,7 +84,7 @@ 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(), null, 0) + "INVALID", null, emptyList(), null, 0, 0) fun isMediaNotification(sbn: StatusBarNotification): Boolean { return sbn.notification.isMediaNotification() @@ -852,15 +852,16 @@ class MediaDataManager( * until the next refresh-round before UI becomes visible. True by default to take in place * immediately. * - * @param isSsReactivated indicates transition from a state with no active media players to - * a state with active media players upon receiving Smartspace media data. + * @param receivedSmartspaceCardLatency is the latency between headphone connects and sysUI + * displays Smartspace media targets. Will be 0 if the data is not activated by Smartspace + * signal. */ fun onMediaDataLoaded( key: String, oldKey: String?, data: MediaData, immediately: Boolean = true, - isSsReactivated: Boolean = false + receivedSmartspaceCardLatency: Int = 0 ) {} /** @@ -869,11 +870,15 @@ class MediaDataManager( * @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. + * + * @param isSsReactivated indicates resume media card is reactivated by Smartspace + * recommendation signal */ fun onSmartspaceMediaDataLoaded( key: String, data: SmartspaceMediaData, - shouldPrioritize: Boolean = false + shouldPrioritize: Boolean = false, + isSsReactivated: Boolean = false ) {} /** Called whenever a previously existing Media notification was removed. */ @@ -909,12 +914,13 @@ class MediaDataManager( packageName(target)?.let { return SmartspaceMediaData(target.smartspaceTargetId, isActive, true, it, target.baseAction, target.iconGrid, - dismissIntent, 0) + dismissIntent, 0, target.creationTimeMillis) } return EMPTY_SMARTSPACE_MEDIA_DATA .copy(targetId = target.smartspaceTargetId, - isActive = isActive, - dismissIntent = dismissIntent) + isActive = isActive, + dismissIntent = dismissIntent, + headphoneConnectionTimeMillis = target.creationTimeMillis) } private fun packageName(target: SmartspaceTarget): String? { diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt index fbfb919ede1e2..bed254fe82497 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt @@ -68,7 +68,7 @@ class MediaDeviceManager @Inject constructor( oldKey: String?, data: MediaData, immediately: Boolean, - isSsReactivated: Boolean + receivedSmartspaceCardLatency: Int ) { if (oldKey != null && oldKey != key) { val oldEntry = entries.remove(oldKey) @@ -200,7 +200,7 @@ class MediaDeviceManager @Inject constructor( @WorkerThread private fun updateCurrent() { val device = localMediaManager.currentConnectedDevice - val route = controller?.let { mr2manager.getRoutingSessionForMediaController(it)} + val route = controller?.let { mr2manager.getRoutingSessionForMediaController(it) } // If we have a controller but get a null route, then don't trust the device val enabled = device != null && (controller == null || route != null) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt index ff085c36ef9c9..0a4b68b501f7b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt @@ -61,7 +61,7 @@ class MediaHost constructor( oldKey: String?, data: MediaData, immediately: Boolean, - isSsReactivated: Boolean + receivedSmartspaceCardLatency: Int ) { if (immediately) { updateViewVisibility() @@ -71,7 +71,8 @@ class MediaHost constructor( override fun onSmartspaceMediaDataLoaded( key: String, data: SmartspaceMediaData, - shouldPrioritize: Boolean + shouldPrioritize: Boolean, + isSsReactivated: Boolean ) { updateViewVisibility() } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt index d8095f3179b30..35f95dd27c1f3 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt @@ -184,7 +184,7 @@ class MediaResumeListener @Inject constructor( oldKey: String?, data: MediaData, immediately: Boolean, - isSsReactivated: Boolean + receivedSmartspaceCardLatency: Int ) { if (useMediaResumption) { // If this had been started from a resume state, disconnect now that it's live diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt index 8bddde839817c..1c448a2ff8c49 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt @@ -96,7 +96,7 @@ class MediaSessionBasedFilter @Inject constructor( oldKey: String?, data: MediaData, immediately: Boolean, - isSsReactivated: Boolean + receivedSmartspaceCardLatency: Int ) { backgroundExecutor.execute { data.token?.let { @@ -143,7 +143,8 @@ class MediaSessionBasedFilter @Inject constructor( override fun onSmartspaceMediaDataLoaded( key: String, data: SmartspaceMediaData, - shouldPrioritize: Boolean + shouldPrioritize: Boolean, + isSsReactivated: Boolean ) { backgroundExecutor.execute { dispatchSmartspaceMediaDataLoaded(key, data) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt index 6f047712c954b..9581a633a8c54 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt @@ -63,7 +63,7 @@ class MediaTimeoutListener @Inject constructor( oldKey: String?, data: MediaData, immediately: Boolean, - isSsReactivated: Boolean + receivedSmartspaceCardLatency: Int ) { var reusedListener: PlaybackStateListener? = null diff --git a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt index 61fdefd4579fe..066a6fbfa8bed 100644 --- a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt @@ -52,5 +52,9 @@ data class SmartspaceMediaData( /** * View's background color. */ - val backgroundColor: Int + val backgroundColor: Int, + /** + * The timestamp in milliseconds that headphone is connected. + */ + val headphoneConnectionTimeMillis: Long ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index 50983704dd445..0e3b5b5c882ba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -242,12 +242,13 @@ public class NotificationMediaManager implements Dumpable { @Override public void onMediaDataLoaded(@NonNull String key, @Nullable String oldKey, @NonNull MediaData data, boolean immediately, - boolean isSsReactivated) { + int receivedSmartspaceCardLatency) { } @Override public void onSmartspaceMediaDataLoaded(@NonNull String key, - @NonNull SmartspaceMediaData data, boolean shouldPrioritize) { + @NonNull SmartspaceMediaData data, boolean shouldPrioritize, + boolean isSsReactivated) { } @Override @@ -316,12 +317,13 @@ public class NotificationMediaManager implements Dumpable { @Override public void onMediaDataLoaded(@NonNull String key, @Nullable String oldKey, @NonNull MediaData data, boolean immediately, - boolean isSsReactivated) { + int receivedSmartspaceCardLatency) { } @Override public void onSmartspaceMediaDataLoaded(@NonNull String key, - @NonNull SmartspaceMediaData data, boolean shouldPrioritize) { + @NonNull SmartspaceMediaData data, boolean shouldPrioritize, + boolean isSsReactivated) { } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt index bbeadf62ae0f0..41ce9417c78f9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt @@ -123,6 +123,7 @@ public class MediaControlPanelTest : SysuiTestCase() { private lateinit var session: MediaSession private val device = MediaDeviceData(true, null, DEVICE_NAME) private val disabledDevice = MediaDeviceData(false, null, "Disabled Device") + private val clock = FakeSystemClock() @JvmField @Rule val mockito = MockitoJUnit.rule() @@ -134,7 +135,7 @@ public class MediaControlPanelTest : SysuiTestCase() { player = MediaControlPanel(context, bgExecutor, activityStarter, mediaViewController, seekBarViewModel, Lazy { mediaDataManager }, keyguardDismissUtil, - mediaOutputDialogFactory, mediaCarouselController, falsingManager) + mediaOutputDialogFactory, mediaCarouselController, falsingManager, clock) whenever(seekBarViewModel.progress).thenReturn(seekBarData) // Mock out a view holder for the player to attach to. 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 f870da3e68d9f..09c83e566ee88 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java @@ -18,6 +18,7 @@ package com.android.systemui.media; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.eq; @@ -83,10 +84,10 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { public void eventNotEmittedWithoutDevice() { // WHEN data source emits an event without device data mManager.onMediaDataLoaded(KEY, null, mMediaData, true /* immediately */, - false /* isSsReactivated */); + 0 /* receivedSmartspaceCardLatency */); // THEN an event isn't emitted verify(mListener, never()).onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), - anyBoolean()); + anyInt()); } @Test @@ -95,7 +96,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { mManager.onMediaDeviceChanged(KEY, null, mDeviceData); // THEN an event isn't emitted verify(mListener, never()).onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), - anyBoolean()); + anyInt()); } @Test @@ -104,11 +105,11 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { mManager.onMediaDeviceChanged(KEY, null, mDeviceData); // WHEN media event is received mManager.onMediaDataLoaded(KEY, null, mMediaData, true /* immediately */, - false /* isSsReactivated */); + 0 /* receivedSmartspaceCardLatency */); // THEN the listener receives a combined event ArgumentCaptor captor = ArgumentCaptor.forClass(MediaData.class); verify(mListener).onMediaDataLoaded(eq(KEY), any(), captor.capture(), anyBoolean(), - anyBoolean()); + anyInt()); assertThat(captor.getValue().getDevice()).isNotNull(); } @@ -116,13 +117,13 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { public void emitEventAfterMediaFirst() { // GIVEN that media event has already been received mManager.onMediaDataLoaded(KEY, null, mMediaData, true /* immediately */, - false /* isSsReactivated */); + 0 /* receivedSmartspaceCardLatency */); // WHEN device event is received mManager.onMediaDeviceChanged(KEY, null, mDeviceData); // THEN the listener receives a combined event ArgumentCaptor captor = ArgumentCaptor.forClass(MediaData.class); verify(mListener).onMediaDataLoaded(eq(KEY), any(), captor.capture(), anyBoolean(), - anyBoolean()); + anyInt()); assertThat(captor.getValue().getDevice()).isNotNull(); } @@ -130,16 +131,16 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { public void migrateKeyMediaFirst() { // GIVEN that media and device info has already been received mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData, true /* immediately */, - false /* isSsReactivated */); + 0 /* receivedSmartspaceCardLatency */); mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData); reset(mListener); // WHEN a key migration event is received mManager.onMediaDataLoaded(KEY, OLD_KEY, mMediaData, true /* immediately */, - false /* isSsReactivated */); + 0 /* receivedSmartspaceCardLatency */); // THEN the listener receives a combined event ArgumentCaptor captor = ArgumentCaptor.forClass(MediaData.class); verify(mListener).onMediaDataLoaded(eq(KEY), eq(OLD_KEY), captor.capture(), anyBoolean(), - anyBoolean()); + anyInt()); assertThat(captor.getValue().getDevice()).isNotNull(); } @@ -147,7 +148,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { public void migrateKeyDeviceFirst() { // GIVEN that media and device info has already been received mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData, true /* immediately */, - false /* isSsReactivated */); + 0 /* receivedSmartspaceCardLatency */); mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData); reset(mListener); // WHEN a key migration event is received @@ -155,7 +156,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { // THEN the listener receives a combined event ArgumentCaptor captor = ArgumentCaptor.forClass(MediaData.class); verify(mListener).onMediaDataLoaded(eq(KEY), eq(OLD_KEY), captor.capture(), anyBoolean(), - anyBoolean()); + anyInt()); assertThat(captor.getValue().getDevice()).isNotNull(); } @@ -163,17 +164,17 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { public void migrateKeyMediaAfter() { // GIVEN that media and device info has already been received mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData, true /* immediately */, - false /* isSsReactivated */); + 0 /* receivedSmartspaceCardLatency */); 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, true /* immediately */, - false /* isSsReactivated */); + 0 /* receivedSmartspaceCardLatency */); // THEN the key has already been migrated ArgumentCaptor captor = ArgumentCaptor.forClass(MediaData.class); verify(mListener).onMediaDataLoaded(eq(KEY), eq(KEY), captor.capture(), anyBoolean(), - anyBoolean()); + anyInt()); assertThat(captor.getValue().getDevice()).isNotNull(); } @@ -181,17 +182,17 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { public void migrateKeyDeviceAfter() { // GIVEN that media and device info has already been received mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData, true /* immediately */, - false /* isSsReactivated */); + 0 /* receivedSmartspaceCardLatency */); mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData); mManager.onMediaDataLoaded(KEY, OLD_KEY, mMediaData, true /* immediately */, - false /* isSsReactivated */); + 0 /* receivedSmartspaceCardLatency */); 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 captor = ArgumentCaptor.forClass(MediaData.class); verify(mListener).onMediaDataLoaded(eq(KEY), eq(KEY), captor.capture(), anyBoolean(), - anyBoolean()); + anyInt()); assertThat(captor.getValue().getDevice()).isNotNull(); } @@ -206,7 +207,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { @Test public void mediaDataRemovedAfterMediaEvent() { mManager.onMediaDataLoaded(KEY, null, mMediaData, true /* immediately */, - false /* isSsReactivated */); + 0 /* receivedSmartspaceCardLatency */); mManager.onMediaDataRemoved(KEY); verify(mListener).onMediaDataRemoved(eq(KEY)); } @@ -222,14 +223,14 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { public void mediaDataKeyUpdated() { // GIVEN that device and media events have already been received mManager.onMediaDataLoaded(KEY, null, mMediaData, true /* immediately */, - false /* isSsReactivated */); + 0 /* receivedSmartspaceCardLatency */); mManager.onMediaDeviceChanged(KEY, null, mDeviceData); // WHEN the key is changed mManager.onMediaDataLoaded("NEW_KEY", KEY, mMediaData, true /* immediately */, - false /* isSsReactivated */); + 0 /* receivedSmartspaceCardLatency */); // THEN the listener gets a load event with the correct keys ArgumentCaptor captor = ArgumentCaptor.forClass(MediaData.class); verify(mListener).onMediaDataLoaded( - eq("NEW_KEY"), any(), captor.capture(), anyBoolean(), anyBoolean()); + eq("NEW_KEY"), any(), captor.capture(), anyBoolean(), anyInt()); } } 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 a435e7961b35c..5a3c43c6cc151 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt @@ -87,7 +87,7 @@ class MediaDataFilterTest : SysuiTestCase() { fun setup() { MockitoAnnotations.initMocks(this) MediaPlayerData.clear() - mediaDataFilter = MediaDataFilter(context, broadcastDispatcher, mediaResumeListener, + mediaDataFilter = MediaDataFilter(context, broadcastDispatcher, lockscreenUserManager, executor, clock) mediaDataFilter.mediaDataManager = mediaDataManager mediaDataFilter.addListener(listener) @@ -107,6 +107,8 @@ class MediaDataFilterTest : SysuiTestCase() { `when`(smartspaceData.isValid).thenReturn(true) `when`(smartspaceData.packageName).thenReturn(PACKAGE) `when`(smartspaceData.recommendations).thenReturn(listOf(smartspaceMediaRecommendationItem)) + `when`(smartspaceData.headphoneConnectionTimeMillis).thenReturn( + clock.currentTimeMillis() - 100) } private fun setUser(id: Int) { @@ -122,7 +124,7 @@ class MediaDataFilterTest : SysuiTestCase() { // THEN we should tell the listener verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataMain), eq(true), - eq(false)) + eq(0)) } @Test @@ -131,7 +133,7 @@ class MediaDataFilterTest : SysuiTestCase() { mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest) // THEN we should NOT tell the listener - verify(listener, never()).onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyBoolean()) + verify(listener, never()).onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt()) } @Test @@ -178,11 +180,11 @@ class MediaDataFilterTest : SysuiTestCase() { // THEN we should add back the guest user media verify(listener).onMediaDataLoaded(eq(KEY_ALT), eq(null), eq(dataGuest), eq(true), - eq(false)) + eq(0)) // but not the main user's verify(listener, never()).onMediaDataLoaded(eq(KEY), any(), eq(dataMain), anyBoolean(), - anyBoolean()) + anyInt()) } @Test @@ -239,7 +241,8 @@ class MediaDataFilterTest : SysuiTestCase() { mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) verify(listener) - .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true)) + .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true), + eq(false)) assertThat(mediaDataFilter.hasActiveMedia()).isTrue() } @@ -249,8 +252,9 @@ class MediaDataFilterTest : SysuiTestCase() { mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) - verify(listener, never()).onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyBoolean()) - verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean()) + verify(listener, never()).onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt()) + verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean(), + anyBoolean()) assertThat(mediaDataFilter.hasActiveMedia()).isFalse() } @@ -262,7 +266,8 @@ class MediaDataFilterTest : SysuiTestCase() { mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) verify(listener) - .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true)) + .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true), + eq(true)) assertThat(mediaDataFilter.hasActiveMedia()).isTrue() } @@ -275,7 +280,8 @@ class MediaDataFilterTest : SysuiTestCase() { clock.advanceTime(SMARTSPACE_MAX_AGE + 100) mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) - verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean()) + verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean(), + anyBoolean()) assertThat(mediaDataFilter.hasActiveMedia()).isFalse() } @@ -287,15 +293,16 @@ class MediaDataFilterTest : SysuiTestCase() { 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), - eq(false)) + eq(0)) // AND we get a smartspace signal mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) // THEN we should tell listeners to treat the media as not active instead verify(listener, never()).onMediaDataLoaded(eq(KEY), eq(KEY), any(), anyBoolean(), + anyInt()) + verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean(), anyBoolean()) - verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean()) assertThat(mediaDataFilter.hasActiveMedia()).isFalse() } @@ -307,7 +314,7 @@ class MediaDataFilterTest : SysuiTestCase() { 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), - eq(false)) + eq(0)) // AND we get a smartspace signal mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) @@ -315,10 +322,11 @@ class MediaDataFilterTest : SysuiTestCase() { // 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), eq(true), - eq(true)) + eq(100)) assertThat(mediaDataFilter.hasActiveMedia()).isTrue() // Smartspace update shouldn't be propagated for the empty rec list. - verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean()) + verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean(), + anyBoolean()) } @Test @@ -327,7 +335,7 @@ class MediaDataFilterTest : SysuiTestCase() { 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), - eq(false)) + eq(0)) // AND we get a smartspace signal mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) @@ -335,11 +343,12 @@ class MediaDataFilterTest : SysuiTestCase() { // 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), eq(true), - eq(true)) + eq(100)) assertThat(mediaDataFilter.hasActiveMedia()).isTrue() // Smartspace update should also be propagated but not prioritized. verify(listener) - .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false)) + .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false), + eq(true)) } @Test @@ -356,13 +365,13 @@ class MediaDataFilterTest : SysuiTestCase() { 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), - eq(false)) + eq(0)) mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) val dataCurrentAndActive = dataCurrent.copy(active = true) verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), eq(dataCurrentAndActive), eq(true), - eq(true)) + eq(100)) mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY) 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 f44cc38c78c07..e2019e089329f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt @@ -160,6 +160,7 @@ class MediaDataManagerTest : SysuiTestCase() { whenever(mediaSmartspaceTarget.smartspaceTargetId).thenReturn(KEY_MEDIA_SMARTSPACE) whenever(mediaSmartspaceTarget.featureType).thenReturn(SmartspaceTarget.FEATURE_MEDIA) whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf(mediaRecommendationItem)) + whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(1234L) } @After @@ -213,7 +214,7 @@ class MediaDataManagerTest : SysuiTestCase() { mediaDataManager.onNotificationAdded(KEY, mediaNotification) mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java)) verify(listener).onMediaDataLoaded(eq(KEY), eq(null), anyObject(), eq(true), - eq(false)) + eq(0)) } @Test @@ -225,7 +226,7 @@ class MediaDataManagerTest : SysuiTestCase() { assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true), - eq(false)) + eq(0)) assertThat(mediaDataCaptor.value!!.active).isTrue() } @@ -248,7 +249,7 @@ class MediaDataManagerTest : SysuiTestCase() { assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true), - eq(false)) + eq(0)) assertThat(mediaDataCaptor.value!!.playbackLocation).isEqualTo( MediaData.PLAYBACK_CAST_REMOTE) } @@ -269,7 +270,7 @@ class MediaDataManagerTest : SysuiTestCase() { assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true), - eq(false)) + eq(0)) val data = mediaDataCaptor.value assertThat(data.resumption).isFalse() mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {})) @@ -278,7 +279,7 @@ class MediaDataManagerTest : SysuiTestCase() { // THEN the media data indicates that it is for resumption verify(listener) .onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true), - eq(false)) + eq(0)) assertThat(mediaDataCaptor.value.resumption).isTrue() assertThat(mediaDataCaptor.value.isPlaying).isFalse() } @@ -293,7 +294,7 @@ class MediaDataManagerTest : SysuiTestCase() { assertThat(foregroundExecutor.runAllReady()).isEqualTo(2) verify(listener) .onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true), - eq(false)) + eq(0)) val data = mediaDataCaptor.value assertThat(data.resumption).isFalse() val resumableData = data.copy(resumeAction = Runnable {}) @@ -305,7 +306,7 @@ class MediaDataManagerTest : SysuiTestCase() { // 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), eq(true), - eq(false)) + eq(0)) assertThat(mediaDataCaptor.value.resumption).isTrue() verify(listener, never()).onMediaDataRemoved(eq(KEY)) // WHEN the second is removed @@ -314,7 +315,7 @@ class MediaDataManagerTest : SysuiTestCase() { verify(listener) .onMediaDataLoaded( eq(PACKAGE_NAME), eq(PACKAGE_NAME), capture(mediaDataCaptor), eq(true), - eq(false)) + eq(0)) assertThat(mediaDataCaptor.value.resumption).isTrue() verify(listener).onMediaDataRemoved(eq(KEY_2)) } @@ -329,7 +330,7 @@ class MediaDataManagerTest : SysuiTestCase() { assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true), - eq(false)) + eq(0)) val data = mediaDataCaptor.value val dataRemoteWithResume = data.copy(resumeAction = Runnable {}, playbackLocation = MediaData.PLAYBACK_CAST_LOCAL) @@ -357,7 +358,7 @@ class MediaDataManagerTest : SysuiTestCase() { // THEN the media data indicates that it is for resumption verify(listener) .onMediaDataLoaded(eq(PACKAGE_NAME), eq(null), capture(mediaDataCaptor), eq(true), - eq(false)) + eq(0)) val data = mediaDataCaptor.value assertThat(data.resumption).isTrue() assertThat(data.song).isEqualTo(SESSION_TITLE) @@ -405,7 +406,7 @@ class MediaDataManagerTest : SysuiTestCase() { assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) verify(listener) .onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true), - eq(false)) + eq(0)) } @Test @@ -415,8 +416,8 @@ class MediaDataManagerTest : SysuiTestCase() { eq(KEY_MEDIA_SMARTSPACE), eq(SmartspaceMediaData(KEY_MEDIA_SMARTSPACE, true /* isActive */, true /*isValid */, PACKAGE_NAME, mediaSmartspaceBaseAction, listOf(mediaRecommendationItem), - DISMISS_INTENT, 0)), - eq(false)) + DISMISS_INTENT, 0, 1234L)), + eq(false), eq(false)) } @Test @@ -427,8 +428,9 @@ class MediaDataManagerTest : SysuiTestCase() { eq(KEY_MEDIA_SMARTSPACE), eq(EMPTY_SMARTSPACE_MEDIA_DATA .copy(targetId = KEY_MEDIA_SMARTSPACE, isActive = true, - isValid = false, dismissIntent = DISMISS_INTENT)), - eq(false)) + isValid = false, dismissIntent = DISMISS_INTENT, + headphoneConnectionTimeMillis = 1234L)), + eq(false), eq(false)) } @Test @@ -447,15 +449,15 @@ class MediaDataManagerTest : SysuiTestCase() { eq(KEY_MEDIA_SMARTSPACE), eq(EMPTY_SMARTSPACE_MEDIA_DATA .copy(targetId = KEY_MEDIA_SMARTSPACE, isActive = true, - isValid = false, dismissIntent = null)), - eq(false)) + isValid = false, dismissIntent = null, headphoneConnectionTimeMillis = 1234L)), + eq(false), eq(false)) } @Test fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_notCallsListener() { smartspaceMediaDataProvider.onTargetsAvailable(listOf()) verify(listener, never()) - .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean()) + .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean(), anyBoolean()) } @Test @@ -479,7 +481,7 @@ class MediaDataManagerTest : SysuiTestCase() { // THEN smartspace signal is ignored verify(listener, never()) - .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean()) + .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean(), anyBoolean()) } @Test @@ -487,7 +489,7 @@ class MediaDataManagerTest : SysuiTestCase() { // GIVEN a media recommendation card is present smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget)) verify(listener).onSmartspaceMediaDataLoaded(eq(KEY_MEDIA_SMARTSPACE), anyObject(), - anyBoolean()) + anyBoolean(), anyBoolean()) // WHEN the media recommendation setting is turned off Settings.Secure.putInt(context.contentResolver, @@ -507,7 +509,7 @@ class MediaDataManagerTest : SysuiTestCase() { assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true), - eq(false)) + eq(0)) assertThat(mediaDataCaptor.value!!.lastActive).isAtLeast(currentTime) } @@ -525,7 +527,7 @@ class MediaDataManagerTest : SysuiTestCase() { // THEN the last active time is not changed verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), capture(mediaDataCaptor), eq(true), - eq(false)) + eq(0)) assertThat(mediaDataCaptor.value.lastActive).isLessThan(currentTime) } @@ -537,7 +539,7 @@ class MediaDataManagerTest : SysuiTestCase() { assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true), - eq(false)) + eq(0)) val data = mediaDataCaptor.value assertThat(data.resumption).isFalse() mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {})) @@ -550,7 +552,7 @@ class MediaDataManagerTest : SysuiTestCase() { // THEN the last active time is not changed verify(listener) .onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true), - eq(false)) + eq(0)) assertThat(mediaDataCaptor.value.resumption).isTrue() assertThat(mediaDataCaptor.value.lastActive).isLessThan(currentTime) } @@ -577,7 +579,7 @@ class MediaDataManagerTest : SysuiTestCase() { // THEN only the first MAX_COMPACT_ACTIONS are actually set verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true), - eq(false)) + eq(0)) 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 b9caab277c4cd..5d53181c8345b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt @@ -37,6 +37,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.any @@ -186,7 +187,7 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() { bgExecutor.runAllReady() fgExecutor.runAllReady() verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), - eq(false)) + eq(0)) } @Test @@ -209,7 +210,7 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() { fgExecutor.runAllReady() // THEN the event is not filtered verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), - eq(false)) + eq(0)) } @Test @@ -239,7 +240,7 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() { fgExecutor.runAllReady() // THEN the event is not filtered verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), - eq(false)) + eq(0)) } @Test @@ -255,14 +256,14 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() { fgExecutor.runAllReady() // THEN the event is not filtered verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), - eq(false)) + eq(0)) // 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), anyBoolean(), anyBoolean()) + eq(KEY), eq(null), eq(mediaData2), anyBoolean(), anyInt()) } @Test @@ -279,7 +280,7 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() { // 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), eq(true), - eq(false)) + eq(0)) } @Test @@ -297,14 +298,14 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() { fgExecutor.runAllReady() // THEN the event is not filtered verify(mediaListener).onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1), eq(true), - eq(false)) + eq(0)) // 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), anyBoolean(), anyBoolean()) + .onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), anyBoolean(), anyInt()) // AND there should be a removed event for key2 verify(mediaListener).onMediaDataRemoved(eq(key2)) } @@ -324,14 +325,14 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() { fgExecutor.runAllReady() // THEN the event is not filtered verify(mediaListener).onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1), eq(true), - eq(false)) + eq(0)) // 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), eq(true), - eq(false)) + eq(0)) } @Test @@ -348,14 +349,14 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() { fgExecutor.runAllReady() // THEN the event is not filtered verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), - eq(false)) + eq(0)) // 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), eq(true), - eq(false)) + eq(0)) } @Test @@ -374,7 +375,7 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() { fgExecutor.runAllReady() // THEN the event is not filtered verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), - eq(false)) + eq(0)) } @Test @@ -397,7 +398,7 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() { fgExecutor.runAllReady() // THEN the key migration event is fired verify(mediaListener).onMediaDataLoaded(eq(key2), eq(key1), eq(mediaData2), eq(true), - eq(false)) + eq(0)) } @Test @@ -427,13 +428,13 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() { fgExecutor.runAllReady() // THEN the key migration event is filtered verify(mediaListener, never()) - .onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), anyBoolean(), anyBoolean()) + .onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), anyBoolean(), anyInt()) // 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), eq(true), - eq(false)) + eq(0)) } }