Loading packages/SystemUI/src/com/android/systemui/flags/Flags.kt +3 −0 Original line number Diff line number Diff line Loading @@ -373,6 +373,9 @@ object Flags { // TODO(b/267166152) : Tracking Bug val MEDIA_RETAIN_RECOMMENDATIONS = unreleasedFlag(916, "media_retain_recommendations") // TODO(b/270437894): Tracking Bug val MEDIA_REMOTE_RESUME = unreleasedFlag(917, "media_remote_resume") // 1000 - dock val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging") Loading packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt +5 −2 Original line number Diff line number Diff line Loading @@ -1339,10 +1339,13 @@ class MediaDataManager( fun onNotificationRemoved(key: String) { Assert.isMainThread() val removed = mediaEntries.remove(key) ?: return val isEligibleForResume = removed.isLocalSession() || (mediaFlags.isRemoteResumeAllowed() && removed.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE) if (keyguardUpdateMonitor.isUserInLockdown(removed.userId)) { logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId) } else if (useMediaResumption && removed.resumeAction != null && removed.isLocalSession()) { } else if (useMediaResumption && removed.resumeAction != null && isEligibleForResume) { convertToResumePlayer(key, removed) } else if (mediaFlags.isRetainingPlayersEnabled()) { handlePossibleRemoval(key, removed, notificationRemoved = true) Loading packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt +8 −2 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.media.controls.pipeline.MediaDataManager import com.android.systemui.media.controls.pipeline.RESUME_MEDIA_TIMEOUT import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.settings.UserTracker import com.android.systemui.tuner.TunerService import com.android.systemui.util.Utils Loading @@ -63,7 +64,8 @@ constructor( private val tunerService: TunerService, private val mediaBrowserFactory: ResumeMediaBrowserFactory, dumpManager: DumpManager, private val systemClock: SystemClock private val systemClock: SystemClock, private val mediaFlags: MediaFlags, ) : MediaDataManager.Listener, Dumpable { private var useMediaResumption: Boolean = Utils.useMediaResumption(context) Loading Loading @@ -231,7 +233,11 @@ constructor( mediaBrowser = null } // If we don't have a resume action, check if we haven't already if (data.resumeAction == null && !data.hasCheckedForResume && data.isLocalSession()) { val isEligibleForResume = data.isLocalSession() || (mediaFlags.isRemoteResumeAllowed() && data.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE) if (data.resumeAction == null && !data.hasCheckedForResume && isEligibleForResume) { // TODO also check for a media button receiver intended for restarting (b/154127084) Log.d(TAG, "Checking for service component for " + data.packageName) val pm = context.packageManager Loading packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt +3 −0 Original line number Diff line number Diff line Loading @@ -61,4 +61,7 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) { /** If true, do not automatically dismiss the recommendation card */ fun isPersistentSsCardEnabled() = featureFlags.isEnabled(Flags.MEDIA_RETAIN_RECOMMENDATIONS) /** Check whether we allow remote media to generate resume controls */ fun isRemoteResumeAllowed() = featureFlags.isEnabled(Flags.MEDIA_REMOTE_RESUME) } packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt +75 −44 Original line number Diff line number Diff line Loading @@ -139,6 +139,7 @@ class MediaDataManagerTest : SysuiTestCase() { @Mock private lateinit var logger: MediaUiEventLogger lateinit var mediaDataManager: MediaDataManager lateinit var mediaNotification: StatusBarNotification lateinit var remoteCastNotification: StatusBarNotification @Captor lateinit var mediaDataCaptor: ArgumentCaptor<MediaData> private val clock = FakeSystemClock() @Mock private lateinit var tunerService: TunerService Loading Loading @@ -207,6 +208,20 @@ class MediaDataManagerTest : SysuiTestCase() { } build() } remoteCastNotification = SbnBuilder().run { setPkg(SYSTEM_PACKAGE_NAME) modifyNotification(context).also { it.setSmallIcon(android.R.drawable.ic_media_pause) it.setStyle( MediaStyle().apply { setMediaSession(session.sessionToken) setRemotePlaybackInfo("Remote device", 0, null) } ) } build() } metadataBuilder = MediaMetadata.Builder().apply { putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST) Loading Loading @@ -247,6 +262,7 @@ class MediaDataManagerTest : SysuiTestCase() { whenever(mediaFlags.isExplicitIndicatorEnabled()).thenReturn(true) whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(false) whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false) whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(false) whenever(logger.getNewInstanceId()).thenReturn(instanceIdSequence.newInstanceId()) whenever(keyguardUpdateMonitor.isUserInLockdown(any())).thenReturn(false) } Loading Loading @@ -400,33 +416,8 @@ class MediaDataManagerTest : SysuiTestCase() { @Test fun testOnNotificationAdded_isRcn_markedRemote() { val rcn = SbnBuilder().run { setPkg(SYSTEM_PACKAGE_NAME) modifyNotification(context).also { it.setSmallIcon(android.R.drawable.ic_media_pause) it.setStyle( MediaStyle().apply { setMediaSession(session.sessionToken) setRemotePlaybackInfo("Remote device", 0, null) } ) } build() } addNotificationAndLoad(remoteCastNotification) mediaDataManager.onNotificationAdded(KEY, rcn) assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) verify(listener) .onMediaDataLoaded( eq(KEY), eq(null), capture(mediaDataCaptor), eq(true), eq(0), eq(false) ) assertThat(mediaDataCaptor.value!!.playbackLocation) .isEqualTo(MediaData.PLAYBACK_CAST_REMOTE) verify(logger) Loading Loading @@ -709,6 +700,56 @@ class MediaDataManagerTest : SysuiTestCase() { verify(listener).onMediaDataRemoved(eq(KEY)) } @Test fun testOnNotificationRemoved_withResumption_isRemoteAndRemoteAllowed() { // With the flag enabled to allow remote media to resume whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(true) // GIVEN that the manager has a notification with a resume action, but is not local whenever(controller.metadata).thenReturn(metadataBuilder.build()) whenever(playbackInfo.playbackType) .thenReturn(MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE) addNotificationAndLoad() val data = mediaDataCaptor.value val dataRemoteWithResume = data.copy(resumeAction = Runnable {}, playbackLocation = MediaData.PLAYBACK_CAST_LOCAL) mediaDataManager.onMediaDataLoaded(KEY, null, dataRemoteWithResume) // WHEN the notification is removed mediaDataManager.onNotificationRemoved(KEY) // THEN the media data is converted to a resume state verify(listener) .onMediaDataLoaded( eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true), eq(0), eq(false) ) assertThat(mediaDataCaptor.value.resumption).isTrue() } @Test fun testOnNotificationRemoved_withResumption_isRcnAndRemoteAllowed() { // With the flag enabled to allow remote media to resume whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(true) // GIVEN that the manager has a remote cast notification addNotificationAndLoad(remoteCastNotification) val data = mediaDataCaptor.value assertThat(data.playbackLocation).isEqualTo(MediaData.PLAYBACK_CAST_REMOTE) val dataRemoteWithResume = data.copy(resumeAction = Runnable {}) mediaDataManager.onMediaDataLoaded(KEY, null, dataRemoteWithResume) // WHEN the RCN is removed mediaDataManager.onNotificationRemoved(KEY) // THEN the media data is removed verify(listener).onMediaDataRemoved(eq(KEY)) } @Test fun testOnNotificationRemoved_withResumption_tooManyPlayers() { // Given the maximum number of resume controls already Loading Loading @@ -1654,22 +1695,7 @@ class MediaDataManagerTest : SysuiTestCase() { ) // update to remote cast val rcn = SbnBuilder().run { setPkg(SYSTEM_PACKAGE_NAME) // System package modifyNotification(context).also { it.setSmallIcon(android.R.drawable.ic_media_pause) it.setStyle( MediaStyle().apply { setMediaSession(session.sessionToken) setRemotePlaybackInfo("Remote device", 0, null) } ) } build() } mediaDataManager.onNotificationAdded(KEY, rcn) mediaDataManager.onNotificationAdded(KEY, remoteCastNotification) assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) verify(logger) Loading Loading @@ -2038,9 +2064,14 @@ class MediaDataManagerTest : SysuiTestCase() { verify(listener).onMediaDataRemoved(eq(KEY)) } /** Helper function to add a media notification and capture the resulting MediaData */ /** Helper function to add a basic media notification and capture the resulting MediaData */ private fun addNotificationAndLoad() { mediaDataManager.onNotificationAdded(KEY, mediaNotification) addNotificationAndLoad(mediaNotification) } /** Helper function to add the given notification and capture the resulting MediaData */ private fun addNotificationAndLoad(sbn: StatusBarNotification) { mediaDataManager.onNotificationAdded(KEY, sbn) assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) verify(listener) Loading Loading
packages/SystemUI/src/com/android/systemui/flags/Flags.kt +3 −0 Original line number Diff line number Diff line Loading @@ -373,6 +373,9 @@ object Flags { // TODO(b/267166152) : Tracking Bug val MEDIA_RETAIN_RECOMMENDATIONS = unreleasedFlag(916, "media_retain_recommendations") // TODO(b/270437894): Tracking Bug val MEDIA_REMOTE_RESUME = unreleasedFlag(917, "media_remote_resume") // 1000 - dock val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging") Loading
packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt +5 −2 Original line number Diff line number Diff line Loading @@ -1339,10 +1339,13 @@ class MediaDataManager( fun onNotificationRemoved(key: String) { Assert.isMainThread() val removed = mediaEntries.remove(key) ?: return val isEligibleForResume = removed.isLocalSession() || (mediaFlags.isRemoteResumeAllowed() && removed.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE) if (keyguardUpdateMonitor.isUserInLockdown(removed.userId)) { logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId) } else if (useMediaResumption && removed.resumeAction != null && removed.isLocalSession()) { } else if (useMediaResumption && removed.resumeAction != null && isEligibleForResume) { convertToResumePlayer(key, removed) } else if (mediaFlags.isRetainingPlayersEnabled()) { handlePossibleRemoval(key, removed, notificationRemoved = true) Loading
packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt +8 −2 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.media.controls.pipeline.MediaDataManager import com.android.systemui.media.controls.pipeline.RESUME_MEDIA_TIMEOUT import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.settings.UserTracker import com.android.systemui.tuner.TunerService import com.android.systemui.util.Utils Loading @@ -63,7 +64,8 @@ constructor( private val tunerService: TunerService, private val mediaBrowserFactory: ResumeMediaBrowserFactory, dumpManager: DumpManager, private val systemClock: SystemClock private val systemClock: SystemClock, private val mediaFlags: MediaFlags, ) : MediaDataManager.Listener, Dumpable { private var useMediaResumption: Boolean = Utils.useMediaResumption(context) Loading Loading @@ -231,7 +233,11 @@ constructor( mediaBrowser = null } // If we don't have a resume action, check if we haven't already if (data.resumeAction == null && !data.hasCheckedForResume && data.isLocalSession()) { val isEligibleForResume = data.isLocalSession() || (mediaFlags.isRemoteResumeAllowed() && data.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE) if (data.resumeAction == null && !data.hasCheckedForResume && isEligibleForResume) { // TODO also check for a media button receiver intended for restarting (b/154127084) Log.d(TAG, "Checking for service component for " + data.packageName) val pm = context.packageManager Loading
packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt +3 −0 Original line number Diff line number Diff line Loading @@ -61,4 +61,7 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) { /** If true, do not automatically dismiss the recommendation card */ fun isPersistentSsCardEnabled() = featureFlags.isEnabled(Flags.MEDIA_RETAIN_RECOMMENDATIONS) /** Check whether we allow remote media to generate resume controls */ fun isRemoteResumeAllowed() = featureFlags.isEnabled(Flags.MEDIA_REMOTE_RESUME) }
packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt +75 −44 Original line number Diff line number Diff line Loading @@ -139,6 +139,7 @@ class MediaDataManagerTest : SysuiTestCase() { @Mock private lateinit var logger: MediaUiEventLogger lateinit var mediaDataManager: MediaDataManager lateinit var mediaNotification: StatusBarNotification lateinit var remoteCastNotification: StatusBarNotification @Captor lateinit var mediaDataCaptor: ArgumentCaptor<MediaData> private val clock = FakeSystemClock() @Mock private lateinit var tunerService: TunerService Loading Loading @@ -207,6 +208,20 @@ class MediaDataManagerTest : SysuiTestCase() { } build() } remoteCastNotification = SbnBuilder().run { setPkg(SYSTEM_PACKAGE_NAME) modifyNotification(context).also { it.setSmallIcon(android.R.drawable.ic_media_pause) it.setStyle( MediaStyle().apply { setMediaSession(session.sessionToken) setRemotePlaybackInfo("Remote device", 0, null) } ) } build() } metadataBuilder = MediaMetadata.Builder().apply { putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST) Loading Loading @@ -247,6 +262,7 @@ class MediaDataManagerTest : SysuiTestCase() { whenever(mediaFlags.isExplicitIndicatorEnabled()).thenReturn(true) whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(false) whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false) whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(false) whenever(logger.getNewInstanceId()).thenReturn(instanceIdSequence.newInstanceId()) whenever(keyguardUpdateMonitor.isUserInLockdown(any())).thenReturn(false) } Loading Loading @@ -400,33 +416,8 @@ class MediaDataManagerTest : SysuiTestCase() { @Test fun testOnNotificationAdded_isRcn_markedRemote() { val rcn = SbnBuilder().run { setPkg(SYSTEM_PACKAGE_NAME) modifyNotification(context).also { it.setSmallIcon(android.R.drawable.ic_media_pause) it.setStyle( MediaStyle().apply { setMediaSession(session.sessionToken) setRemotePlaybackInfo("Remote device", 0, null) } ) } build() } addNotificationAndLoad(remoteCastNotification) mediaDataManager.onNotificationAdded(KEY, rcn) assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) verify(listener) .onMediaDataLoaded( eq(KEY), eq(null), capture(mediaDataCaptor), eq(true), eq(0), eq(false) ) assertThat(mediaDataCaptor.value!!.playbackLocation) .isEqualTo(MediaData.PLAYBACK_CAST_REMOTE) verify(logger) Loading Loading @@ -709,6 +700,56 @@ class MediaDataManagerTest : SysuiTestCase() { verify(listener).onMediaDataRemoved(eq(KEY)) } @Test fun testOnNotificationRemoved_withResumption_isRemoteAndRemoteAllowed() { // With the flag enabled to allow remote media to resume whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(true) // GIVEN that the manager has a notification with a resume action, but is not local whenever(controller.metadata).thenReturn(metadataBuilder.build()) whenever(playbackInfo.playbackType) .thenReturn(MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE) addNotificationAndLoad() val data = mediaDataCaptor.value val dataRemoteWithResume = data.copy(resumeAction = Runnable {}, playbackLocation = MediaData.PLAYBACK_CAST_LOCAL) mediaDataManager.onMediaDataLoaded(KEY, null, dataRemoteWithResume) // WHEN the notification is removed mediaDataManager.onNotificationRemoved(KEY) // THEN the media data is converted to a resume state verify(listener) .onMediaDataLoaded( eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true), eq(0), eq(false) ) assertThat(mediaDataCaptor.value.resumption).isTrue() } @Test fun testOnNotificationRemoved_withResumption_isRcnAndRemoteAllowed() { // With the flag enabled to allow remote media to resume whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(true) // GIVEN that the manager has a remote cast notification addNotificationAndLoad(remoteCastNotification) val data = mediaDataCaptor.value assertThat(data.playbackLocation).isEqualTo(MediaData.PLAYBACK_CAST_REMOTE) val dataRemoteWithResume = data.copy(resumeAction = Runnable {}) mediaDataManager.onMediaDataLoaded(KEY, null, dataRemoteWithResume) // WHEN the RCN is removed mediaDataManager.onNotificationRemoved(KEY) // THEN the media data is removed verify(listener).onMediaDataRemoved(eq(KEY)) } @Test fun testOnNotificationRemoved_withResumption_tooManyPlayers() { // Given the maximum number of resume controls already Loading Loading @@ -1654,22 +1695,7 @@ class MediaDataManagerTest : SysuiTestCase() { ) // update to remote cast val rcn = SbnBuilder().run { setPkg(SYSTEM_PACKAGE_NAME) // System package modifyNotification(context).also { it.setSmallIcon(android.R.drawable.ic_media_pause) it.setStyle( MediaStyle().apply { setMediaSession(session.sessionToken) setRemotePlaybackInfo("Remote device", 0, null) } ) } build() } mediaDataManager.onNotificationAdded(KEY, rcn) mediaDataManager.onNotificationAdded(KEY, remoteCastNotification) assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) verify(logger) Loading Loading @@ -2038,9 +2064,14 @@ class MediaDataManagerTest : SysuiTestCase() { verify(listener).onMediaDataRemoved(eq(KEY)) } /** Helper function to add a media notification and capture the resulting MediaData */ /** Helper function to add a basic media notification and capture the resulting MediaData */ private fun addNotificationAndLoad() { mediaDataManager.onNotificationAdded(KEY, mediaNotification) addNotificationAndLoad(mediaNotification) } /** Helper function to add the given notification and capture the resulting MediaData */ private fun addNotificationAndLoad(sbn: StatusBarNotification) { mediaDataManager.onNotificationAdded(KEY, sbn) assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) verify(listener) Loading