Skip to content

Commit 0a24f79

Browse files
Beth ThibodeauAndroid Build Coastguard Worker
authored andcommitted
Improve user handling when querying for resumable media
- Before trying to query recent media from a saved component, check whether the current user actually has that component installed - Track user when creating the MediaBrowser, in case the user changes before the MBS returns a result Test: atest MediaResumeListenerTest Bug: 284297711 (cherry picked from commit e566a250ad61e269119b475c7ebdae6ca962c4a7) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:01d527cdfd135aa8a2e76c693137fbba5d109a8b) Merged-In: I838ff0e125acadabc8436a00dbff707cc4be6249 Change-Id: I838ff0e125acadabc8436a00dbff707cc4be6249
1 parent 6801e48 commit 0a24f79

5 files changed

Lines changed: 120 additions & 18 deletions

File tree

packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,9 @@ constructor(
122122
Log.e(TAG, "Error getting package information", e)
123123
}
124124

125-
Log.d(TAG, "Adding resume controls $desc")
125+
Log.d(TAG, "Adding resume controls for ${browser.userId}: $desc")
126126
mediaDataManager.addResumptionControls(
127-
currentUserId,
127+
browser.userId,
128128
desc,
129129
resumeAction,
130130
token,
@@ -196,7 +196,11 @@ constructor(
196196
}
197197
resumeComponents.add(component to lastPlayed)
198198
}
199-
Log.d(TAG, "loaded resume components ${resumeComponents.toArray().contentToString()}")
199+
Log.d(
200+
TAG,
201+
"loaded resume components for $currentUserId: " +
202+
"${resumeComponents.toArray().contentToString()}"
203+
)
200204

201205
if (needsUpdate) {
202206
// Save any missing times that we had to fill in
@@ -210,11 +214,21 @@ constructor(
210214
return
211215
}
212216

217+
val pm = context.packageManager
213218
val now = systemClock.currentTimeMillis()
214219
resumeComponents.forEach {
215220
if (now.minus(it.second) <= RESUME_MEDIA_TIMEOUT) {
216-
val browser = mediaBrowserFactory.create(mediaBrowserCallback, it.first)
217-
browser.findRecentMedia()
221+
// Verify that the service exists for this user
222+
val intent = Intent(MediaBrowserService.SERVICE_INTERFACE)
223+
intent.component = it.first
224+
val inf = pm.resolveServiceAsUser(intent, 0, currentUserId)
225+
if (inf != null) {
226+
val browser =
227+
mediaBrowserFactory.create(mediaBrowserCallback, it.first, currentUserId)
228+
browser.findRecentMedia()
229+
} else {
230+
Log.d(TAG, "User $currentUserId does not have component ${it.first}")
231+
}
218232
}
219233
}
220234
}
@@ -244,7 +258,7 @@ constructor(
244258
Log.d(TAG, "Checking for service component for " + data.packageName)
245259
val pm = context.packageManager
246260
val serviceIntent = Intent(MediaBrowserService.SERVICE_INTERFACE)
247-
val resumeInfo = pm.queryIntentServices(serviceIntent, 0)
261+
val resumeInfo = pm.queryIntentServicesAsUser(serviceIntent, 0, currentUserId)
248262

249263
val inf = resumeInfo?.filter { it.serviceInfo.packageName == data.packageName }
250264
if (inf != null && inf.size > 0) {
@@ -280,13 +294,17 @@ constructor(
280294
browser: ResumeMediaBrowser
281295
) {
282296
// Since this is a test, just save the component for later
283-
Log.d(TAG, "Can get resumable media from $componentName")
297+
Log.d(
298+
TAG,
299+
"Can get resumable media for ${browser.userId} from $componentName"
300+
)
284301
mediaDataManager.setResumeAction(key, getResumeAction(componentName))
285302
updateResumptionList(componentName)
286303
mediaBrowser = null
287304
}
288305
},
289-
componentName
306+
componentName,
307+
currentUserId
290308
)
291309
mediaBrowser?.testConnection()
292310
}
@@ -326,7 +344,7 @@ constructor(
326344
/** Get a runnable which will resume media playback */
327345
private fun getResumeAction(componentName: ComponentName): Runnable {
328346
return Runnable {
329-
mediaBrowser = mediaBrowserFactory.create(null, componentName)
347+
mediaBrowser = mediaBrowserFactory.create(null, componentName, currentUserId)
330348
mediaBrowser?.restart()
331349
}
332350
}

packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.android.systemui.media.controls.resume;
1818

1919
import android.annotation.Nullable;
20+
import android.annotation.UserIdInt;
2021
import android.app.PendingIntent;
2122
import android.content.ComponentName;
2223
import android.content.Context;
@@ -53,6 +54,7 @@ public class ResumeMediaBrowser {
5354
private final ResumeMediaBrowserLogger mLogger;
5455
private final ComponentName mComponentName;
5556
private final MediaController.Callback mMediaControllerCallback = new SessionDestroyCallback();
57+
@UserIdInt private final int mUserId;
5658

5759
private MediaBrowser mMediaBrowser;
5860
@Nullable private MediaController mMediaController;
@@ -62,18 +64,21 @@ public class ResumeMediaBrowser {
6264
* @param context the context
6365
* @param callback used to report media items found
6466
* @param componentName Component name of the MediaBrowserService this browser will connect to
67+
* @param userId ID of the current user
6568
*/
6669
public ResumeMediaBrowser(
6770
Context context,
6871
@Nullable Callback callback,
6972
ComponentName componentName,
7073
MediaBrowserFactory browserFactory,
71-
ResumeMediaBrowserLogger logger) {
74+
ResumeMediaBrowserLogger logger,
75+
@UserIdInt int userId) {
7276
mContext = context;
7377
mCallback = callback;
7478
mComponentName = componentName;
7579
mBrowserFactory = browserFactory;
7680
mLogger = logger;
81+
mUserId = userId;
7782
}
7883

7984
/**
@@ -284,6 +289,14 @@ protected MediaController createMediaController(MediaSession.Token token) {
284289
return new MediaController(mContext, token);
285290
}
286291

292+
/**
293+
* Get the ID of the user associated with this broswer
294+
* @return the user ID
295+
*/
296+
public @UserIdInt int getUserId() {
297+
return mUserId;
298+
}
299+
287300
/**
288301
* Get the media session token
289302
* @return the token, or null if the MediaBrowser is null or disconnected

packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserFactory.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.android.systemui.media.controls.resume;
1818

19+
import android.annotation.UserIdInt;
1920
import android.content.ComponentName;
2021
import android.content.Context;
2122

@@ -42,10 +43,12 @@ public ResumeMediaBrowserFactory(
4243
*
4344
* @param callback will be called on connection or error, and addTrack when media item found
4445
* @param componentName component to browse
46+
* @param userId ID of the current user
4547
* @return
4648
*/
4749
public ResumeMediaBrowser create(ResumeMediaBrowser.Callback callback,
48-
ComponentName componentName) {
49-
return new ResumeMediaBrowser(mContext, callback, componentName, mBrowserFactory, mLogger);
50+
ComponentName componentName, @UserIdInt int userId) {
51+
return new ResumeMediaBrowser(mContext, callback, componentName, mBrowserFactory, mLogger,
52+
userId);
5053
}
5154
}

packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ class MediaResumeListenerTest : SysuiTestCase() {
9898
@Captor lateinit var callbackCaptor: ArgumentCaptor<ResumeMediaBrowser.Callback>
9999
@Captor lateinit var actionCaptor: ArgumentCaptor<Runnable>
100100
@Captor lateinit var componentCaptor: ArgumentCaptor<String>
101+
@Captor lateinit var userIdCaptor: ArgumentCaptor<Int>
102+
@Captor lateinit var userCallbackCaptor: ArgumentCaptor<UserTracker.Callback>
101103

102104
private lateinit var executor: FakeExecutor
103105
private lateinit var data: MediaData
@@ -124,7 +126,7 @@ class MediaResumeListenerTest : SysuiTestCase() {
124126
)
125127
Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 1)
126128

127-
whenever(resumeBrowserFactory.create(capture(callbackCaptor), any()))
129+
whenever(resumeBrowserFactory.create(capture(callbackCaptor), any(), capture(userIdCaptor)))
128130
.thenReturn(resumeBrowser)
129131

130132
// resume components are stored in sharedpreferences
@@ -334,6 +336,7 @@ class MediaResumeListenerTest : SysuiTestCase() {
334336
@Test
335337
fun testOnUserUnlock_loadsTracks() {
336338
// Set up mock service to successfully find valid media
339+
setUpMbsWithValidResolveInfo()
337340
val description = MediaDescription.Builder().setTitle(TITLE).build()
338341
val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
339342
whenever(resumeBrowser.token).thenReturn(token)
@@ -417,6 +420,7 @@ class MediaResumeListenerTest : SysuiTestCase() {
417420
@Test
418421
fun testLoadComponents_recentlyPlayed_adds() {
419422
// Set up browser to return successfully
423+
setUpMbsWithValidResolveInfo()
420424
val description = MediaDescription.Builder().setTitle(TITLE).build()
421425
val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
422426
whenever(resumeBrowser.token).thenReturn(token)
@@ -600,7 +604,7 @@ class MediaResumeListenerTest : SysuiTestCase() {
600604

601605
// Set up our factory to return a new browser so we can verify we disconnected the old one
602606
val newResumeBrowser = mock(ResumeMediaBrowser::class.java)
603-
whenever(resumeBrowserFactory.create(capture(callbackCaptor), any()))
607+
whenever(resumeBrowserFactory.create(capture(callbackCaptor), any(), anyInt()))
604608
.thenReturn(newResumeBrowser)
605609

606610
// When the resume action is run
@@ -610,6 +614,66 @@ class MediaResumeListenerTest : SysuiTestCase() {
610614
verify(resumeBrowser).disconnect()
611615
}
612616

617+
@Test
618+
fun testUserUnlocked_userChangeWhileQuerying() {
619+
val firstUserId = 1
620+
val secondUserId = 2
621+
val description = MediaDescription.Builder().setTitle(TITLE).build()
622+
val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
623+
624+
setUpMbsWithValidResolveInfo()
625+
whenever(resumeBrowser.token).thenReturn(token)
626+
whenever(resumeBrowser.appIntent).thenReturn(pendingIntent)
627+
628+
val unlockIntent =
629+
Intent(Intent.ACTION_USER_UNLOCKED).apply {
630+
putExtra(Intent.EXTRA_USER_HANDLE, firstUserId)
631+
}
632+
verify(userTracker).addCallback(capture(userCallbackCaptor), any())
633+
634+
// When the first user unlocks and we query their recent media
635+
userCallbackCaptor.value.onUserChanged(firstUserId, context)
636+
resumeListener.userUnlockReceiver.onReceive(context, unlockIntent)
637+
whenever(resumeBrowser.userId).thenReturn(userIdCaptor.value)
638+
verify(resumeBrowser, times(3)).findRecentMedia()
639+
640+
// And the user changes before the MBS response is received
641+
userCallbackCaptor.value.onUserChanged(secondUserId, context)
642+
callbackCaptor.value.addTrack(description, component, resumeBrowser)
643+
644+
// Then the loaded media is correctly associated with the first user
645+
verify(mediaDataManager)
646+
.addResumptionControls(
647+
eq(firstUserId),
648+
eq(description),
649+
any(),
650+
eq(token),
651+
eq(PACKAGE_NAME),
652+
eq(pendingIntent),
653+
eq(PACKAGE_NAME)
654+
)
655+
}
656+
657+
@Test
658+
fun testUserUnlocked_noComponent_doesNotQuery() {
659+
// Set up a valid MBS, but user does not have the service available
660+
setUpMbsWithValidResolveInfo()
661+
val pm = mock(PackageManager::class.java)
662+
whenever(mockContext.packageManager).thenReturn(pm)
663+
whenever(pm.resolveServiceAsUser(any(), anyInt(), anyInt())).thenReturn(null)
664+
665+
val unlockIntent =
666+
Intent(Intent.ACTION_USER_UNLOCKED).apply {
667+
putExtra(Intent.EXTRA_USER_HANDLE, context.userId)
668+
}
669+
670+
// When the user is unlocked, but does not have the component installed
671+
resumeListener.userUnlockReceiver.onReceive(context, unlockIntent)
672+
673+
// Then we never attempt to connect to it
674+
verify(resumeBrowser, never()).findRecentMedia()
675+
}
676+
613677
/** Sets up mocks to successfully find a MBS that returns valid media. */
614678
private fun setUpMbsWithValidResolveInfo() {
615679
val pm = mock(PackageManager::class.java)
@@ -620,6 +684,8 @@ class MediaResumeListenerTest : SysuiTestCase() {
620684
resolveInfo.serviceInfo = serviceInfo
621685
resolveInfo.serviceInfo.name = CLASS_NAME
622686
val resumeInfo = listOf(resolveInfo)
623-
whenever(pm.queryIntentServices(any(), anyInt())).thenReturn(resumeInfo)
687+
whenever(pm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(resumeInfo)
688+
whenever(pm.resolveServiceAsUser(any(), anyInt(), anyInt())).thenReturn(resolveInfo)
689+
whenever(pm.getApplicationLabel(any())).thenReturn(PACKAGE_NAME)
624690
}
625691
}

packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserTest.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ public class ResumeMediaBrowserTest : SysuiTestCase() {
9393
component,
9494
browserFactory,
9595
logger,
96-
mediaController
96+
mediaController,
97+
context.userId,
9798
)
9899
}
99100

@@ -381,8 +382,9 @@ public class ResumeMediaBrowserTest : SysuiTestCase() {
381382
componentName: ComponentName,
382383
browserFactory: MediaBrowserFactory,
383384
logger: ResumeMediaBrowserLogger,
384-
private val fakeController: MediaController
385-
) : ResumeMediaBrowser(context, callback, componentName, browserFactory, logger) {
385+
private val fakeController: MediaController,
386+
userId: Int,
387+
) : ResumeMediaBrowser(context, callback, componentName, browserFactory, logger, userId) {
386388

387389
override fun createMediaController(token: MediaSession.Token): MediaController {
388390
return fakeController

0 commit comments

Comments
 (0)