Add flag to allow remote sessions to be resumable

If the flag is enabled, media which is location PLAYBACK_CAST_LOCAL will
be allowed to persist in the resume state (under the same conditions as
local media controls). RCNs will still not be allowed to resume.

Flag: MEDIA_REMOTE_RESUME
Test: atest MediaDataManagerTest MediaResumeListenerTest
Bug: 269637129
Bug: 270437894
Change-Id: Idd0313c0c49f768453e781853edf7f818d6b8ab0
This commit is contained in:
Beth Thibodeau
2023-02-23 11:09:04 -06:00
parent fa013ccc8e
commit 0238730e7f
6 changed files with 133 additions and 53 deletions

View File

@@ -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")

View File

@@ -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)

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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)
@@ -646,6 +637,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
@@ -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)

View File

@@ -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())
@@ -243,6 +248,32 @@ class MediaResumeListenerTest : SysuiTestCase() {
verify(mediaDataManager, never()).setResumeAction(any(), any())
}
@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)