diff options
6 files changed, 133 insertions, 53 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 6bc1eddf26f7..059b3bda0172 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -367,6 +367,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") diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt index f5558a240a70..0c7fdfdeb523 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt @@ -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) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt index 2d10b823f784..2af21c4dbd9f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt @@ -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 @@ -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) @@ -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 diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt index c3fa76ec9433..9bc66f6c98d0 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt @@ -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) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt index a07a714ebc77..bf8d1ecbe1d4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt @@ -137,6 +137,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 @@ -205,6 +206,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) @@ -244,6 +259,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) } @@ -404,33 +420,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) @@ -647,6 +638,56 @@ class MediaDataManagerTest : SysuiTestCase() { } @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 val desc = @@ -1526,22 +1567,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) @@ -1911,9 +1937,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) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt index 136ace173795..4dfa6261b868 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt @@ -38,6 +38,7 @@ import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.media.controls.models.player.MediaDeviceData 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.concurrency.FakeExecutor @@ -92,6 +93,7 @@ class MediaResumeListenerTest : SysuiTestCase() { @Mock private lateinit var mockContext: Context @Mock private lateinit var pendingIntent: PendingIntent @Mock private lateinit var dumpManager: DumpManager + @Mock private lateinit var mediaFlags: MediaFlags @Captor lateinit var callbackCaptor: ArgumentCaptor<ResumeMediaBrowser.Callback> @Captor lateinit var actionCaptor: ArgumentCaptor<Runnable> @@ -134,6 +136,7 @@ class MediaResumeListenerTest : SysuiTestCase() { whenever(mockContext.packageManager).thenReturn(context.packageManager) whenever(mockContext.contentResolver).thenReturn(context.contentResolver) whenever(mockContext.userId).thenReturn(context.userId) + whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(false) executor = FakeExecutor(clock) resumeListener = @@ -146,7 +149,8 @@ class MediaResumeListenerTest : SysuiTestCase() { tunerService, resumeBrowserFactory, dumpManager, - clock + clock, + mediaFlags, ) resumeListener.setManager(mediaDataManager) mediaDataManager.addListener(resumeListener) @@ -188,7 +192,8 @@ class MediaResumeListenerTest : SysuiTestCase() { tunerService, resumeBrowserFactory, dumpManager, - clock + clock, + mediaFlags, ) listener.setManager(mediaDataManager) verify(broadcastDispatcher, never()) @@ -244,6 +249,32 @@ class MediaResumeListenerTest : SysuiTestCase() { } @Test + fun testOnLoad_localCast_remoteResumeAllowed_doesCheck() { + // If local cast media is allowed to resume + whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(true) + + // When media data is loaded that has not been checked yet, and is a local cast + val dataCast = data.copy(playbackLocation = MediaData.PLAYBACK_CAST_LOCAL) + resumeListener.onMediaDataLoaded(KEY, null, dataCast) + + // Then we report back to the manager + verify(mediaDataManager).setResumeAction(KEY, null) + } + + @Test + fun testOnLoad_remoteCast_remoteResumeAllowed_doesCheck() { + // If local cast media is allowed to resume + whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(true) + + // When media data is loaded that has not been checked yet, and is a remote cast + val dataRcn = data.copy(playbackLocation = MediaData.PLAYBACK_CAST_REMOTE) + resumeListener.onMediaDataLoaded(KEY, null, dataRcn) + + // Then we do not take action + verify(mediaDataManager, never()).setResumeAction(any(), any()) + } + + @Test fun testOnLoad_checksForResume_hasService() { setUpMbsWithValidResolveInfo() @@ -389,7 +420,8 @@ class MediaResumeListenerTest : SysuiTestCase() { tunerService, resumeBrowserFactory, dumpManager, - clock + clock, + mediaFlags, ) resumeListener.setManager(mediaDataManager) mediaDataManager.addListener(resumeListener) @@ -421,7 +453,8 @@ class MediaResumeListenerTest : SysuiTestCase() { tunerService, resumeBrowserFactory, dumpManager, - clock + clock, + mediaFlags, ) resumeListener.setManager(mediaDataManager) mediaDataManager.addListener(resumeListener) @@ -463,7 +496,8 @@ class MediaResumeListenerTest : SysuiTestCase() { tunerService, resumeBrowserFactory, dumpManager, - clock + clock, + mediaFlags, ) resumeListener.setManager(mediaDataManager) mediaDataManager.addListener(resumeListener) |