Skip to content

Commit c1cf4b9

Browse files
Beth Thibodeaudoryiii
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:d61741288b4d7614e4677428aac6418f6f1d79f0) Merged-In: I838ff0e125acadabc8436a00dbff707cc4be6249 Change-Id: I838ff0e125acadabc8436a00dbff707cc4be6249
1 parent f810d81 commit c1cf4b9

File tree

5 files changed

+131
-19
lines changed

5 files changed

+131
-19
lines changed

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

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,16 @@ class MediaResumeListener @Inject constructor(
101101
Log.e(TAG, "Error getting package information", e)
102102
}
103103

104-
Log.d(TAG, "Adding resume controls $desc")
105-
mediaDataManager.addResumptionControls(currentUserId, desc, resumeAction, token,
106-
appName.toString(), appIntent, component.packageName)
104+
Log.d(TAG, "Adding resume controls for ${browser.userId}: $desc")
105+
mediaDataManager.addResumptionControls(
106+
browser.userId,
107+
desc,
108+
resumeAction,
109+
token,
110+
appName.toString(),
111+
appIntent,
112+
component.packageName
113+
)
107114
}
108115
}
109116

@@ -158,7 +165,11 @@ class MediaResumeListener @Inject constructor(
158165
}
159166
resumeComponents.add(component to lastPlayed)
160167
}
161-
Log.d(TAG, "loaded resume components ${resumeComponents.toArray().contentToString()}")
168+
Log.d(
169+
TAG,
170+
"loaded resume components for $currentUserId: " +
171+
"${resumeComponents.toArray().contentToString()}"
172+
)
162173

163174
if (needsUpdate) {
164175
// Save any missing times that we had to fill in
@@ -174,11 +185,21 @@ class MediaResumeListener @Inject constructor(
174185
return
175186
}
176187

188+
val pm = context.packageManager
177189
val now = systemClock.currentTimeMillis()
178190
resumeComponents.forEach {
179191
if (now.minus(it.second) <= RESUME_MEDIA_TIMEOUT) {
180-
val browser = mediaBrowserFactory.create(mediaBrowserCallback, it.first)
181-
browser.findRecentMedia()
192+
// Verify that the service exists for this user
193+
val intent = Intent(MediaBrowserService.SERVICE_INTERFACE)
194+
intent.component = it.first
195+
val inf = pm.resolveServiceAsUser(intent, 0, currentUserId)
196+
if (inf != null) {
197+
val browser =
198+
mediaBrowserFactory.create(mediaBrowserCallback, it.first, currentUserId)
199+
browser.findRecentMedia()
200+
} else {
201+
Log.d(TAG, "User $currentUserId does not have component ${it.first}")
202+
}
182203
}
183204
}
184205
}
@@ -202,7 +223,7 @@ class MediaResumeListener @Inject constructor(
202223
Log.d(TAG, "Checking for service component for " + data.packageName)
203224
val pm = context.packageManager
204225
val serviceIntent = Intent(MediaBrowserService.SERVICE_INTERFACE)
205-
val resumeInfo = pm.queryIntentServices(serviceIntent, 0)
226+
val resumeInfo = pm.queryIntentServicesAsUser(serviceIntent, 0, currentUserId)
206227

207228
val inf = resumeInfo?.filter {
208229
it.serviceInfo.packageName == data.packageName
@@ -244,13 +265,18 @@ class MediaResumeListener @Inject constructor(
244265
browser: ResumeMediaBrowser
245266
) {
246267
// Since this is a test, just save the component for later
247-
Log.d(TAG, "Can get resumable media from $componentName")
268+
Log.d(
269+
TAG,
270+
"Can get resumable media for ${browser.userId} from $componentName"
271+
)
248272
mediaDataManager.setResumeAction(key, getResumeAction(componentName))
249273
updateResumptionList(componentName)
250274
mediaBrowser = null
251275
}
252276
},
253-
componentName)
277+
componentName,
278+
currentUserId
279+
)
254280
mediaBrowser?.testConnection()
255281
}
256282

@@ -290,7 +316,7 @@ class MediaResumeListener @Inject constructor(
290316
*/
291317
private fun getResumeAction(componentName: ComponentName): Runnable {
292318
return Runnable {
293-
mediaBrowser = mediaBrowserFactory.create(null, componentName)
319+
mediaBrowser = mediaBrowserFactory.create(null, componentName, currentUserId)
294320
mediaBrowser?.restart()
295321
}
296322
}

packages/SystemUI/src/com/android/systemui/media/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;
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
/**
@@ -275,6 +280,14 @@ protected MediaController createMediaController(MediaSession.Token token) {
275280
return new MediaController(mContext, token);
276281
}
277282

283+
/**
284+
* Get the ID of the user associated with this broswer
285+
* @return the user ID
286+
*/
287+
public @UserIdInt int getUserId() {
288+
return mUserId;
289+
}
290+
278291
/**
279292
* Get the media session token
280293
* @return the token, or null if the MediaBrowser is null or disconnected

packages/SystemUI/src/com/android/systemui/media/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;
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/MediaResumeListenerTest.kt

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ class MediaResumeListenerTest : SysuiTestCase() {
8787
@Captor lateinit var callbackCaptor: ArgumentCaptor<ResumeMediaBrowser.Callback>
8888
@Captor lateinit var actionCaptor: ArgumentCaptor<Runnable>
8989
@Captor lateinit var componentCaptor: ArgumentCaptor<String>
90+
@Captor lateinit var userIdCaptor: ArgumentCaptor<Int>
9091

9192
private lateinit var executor: FakeExecutor
9293
private lateinit var data: MediaData
@@ -107,7 +108,7 @@ class MediaResumeListenerTest : SysuiTestCase() {
107108
Settings.Secure.putInt(context.contentResolver,
108109
Settings.Secure.MEDIA_CONTROLS_RESUME, 1)
109110

110-
whenever(resumeBrowserFactory.create(capture(callbackCaptor), any()))
111+
whenever(resumeBrowserFactory.create(capture(callbackCaptor), any(), capture(userIdCaptor)))
111112
.thenReturn(resumeBrowser)
112113

113114
// resume components are stored in sharedpreferences
@@ -118,6 +119,7 @@ class MediaResumeListenerTest : SysuiTestCase() {
118119
whenever(sharedPrefsEditor.putString(any(), any())).thenReturn(sharedPrefsEditor)
119120
whenever(mockContext.packageManager).thenReturn(context.packageManager)
120121
whenever(mockContext.contentResolver).thenReturn(context.contentResolver)
122+
whenever(mockContext.userId).thenReturn(context.userId)
121123

122124
executor = FakeExecutor(clock)
123125
resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
@@ -243,6 +245,7 @@ class MediaResumeListenerTest : SysuiTestCase() {
243245
@Test
244246
fun testOnUserUnlock_loadsTracks() {
245247
// Set up mock service to successfully find valid media
248+
setUpMbsWithValidResolveInfo()
246249
val description = MediaDescription.Builder().setTitle(TITLE).build()
247250
val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
248251
whenever(resumeBrowser.token).thenReturn(token)
@@ -316,6 +319,7 @@ class MediaResumeListenerTest : SysuiTestCase() {
316319
@Test
317320
fun testLoadComponents_recentlyPlayed_adds() {
318321
// Set up browser to return successfully
322+
setUpMbsWithValidResolveInfo()
319323
val description = MediaDescription.Builder().setTitle(TITLE).build()
320324
val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
321325
whenever(resumeBrowser.token).thenReturn(token)
@@ -464,7 +468,7 @@ class MediaResumeListenerTest : SysuiTestCase() {
464468

465469
// Set up our factory to return a new browser so we can verify we disconnected the old one
466470
val newResumeBrowser = mock(ResumeMediaBrowser::class.java)
467-
whenever(resumeBrowserFactory.create(capture(callbackCaptor), any()))
471+
whenever(resumeBrowserFactory.create(capture(callbackCaptor), any(), anyInt()))
468472
.thenReturn(newResumeBrowser)
469473

470474
// When the resume action is run
@@ -474,6 +478,68 @@ class MediaResumeListenerTest : SysuiTestCase() {
474478
verify(resumeBrowser).disconnect()
475479
}
476480

481+
@Test
482+
fun testUserUnlocked_userChangeWhileQuerying() {
483+
val firstUserId = context.userId
484+
val secondUserId = firstUserId + 1
485+
val description = MediaDescription.Builder().setTitle(TITLE).build()
486+
val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
487+
488+
setUpMbsWithValidResolveInfo()
489+
whenever(resumeBrowser.token).thenReturn(token)
490+
whenever(resumeBrowser.appIntent).thenReturn(pendingIntent)
491+
492+
val unlockIntent =
493+
Intent(Intent.ACTION_USER_UNLOCKED).apply {
494+
putExtra(Intent.EXTRA_USER_HANDLE, firstUserId)
495+
}
496+
497+
// When the first user unlocks and we query their recent media
498+
resumeListener.userChangeReceiver.onReceive(context, unlockIntent)
499+
whenever(resumeBrowser.userId).thenReturn(userIdCaptor.value)
500+
verify(resumeBrowser, times(3)).findRecentMedia()
501+
502+
// And the user changes before the MBS response is received
503+
val changeIntent =
504+
Intent(Intent.ACTION_USER_SWITCHED).apply {
505+
putExtra(Intent.EXTRA_USER_HANDLE, secondUserId)
506+
}
507+
resumeListener.userChangeReceiver.onReceive(context, changeIntent)
508+
callbackCaptor.value.addTrack(description, component, resumeBrowser)
509+
510+
// Then the loaded media is correctly associated with the first user
511+
verify(mediaDataManager)
512+
.addResumptionControls(
513+
eq(firstUserId),
514+
eq(description),
515+
any(),
516+
eq(token),
517+
eq(PACKAGE_NAME),
518+
eq(pendingIntent),
519+
eq(PACKAGE_NAME)
520+
)
521+
}
522+
523+
@Test
524+
fun testUserUnlocked_noComponent_doesNotQuery() {
525+
// Set up a valid MBS, but user does not have the service available
526+
setUpMbsWithValidResolveInfo()
527+
val pm = mock(PackageManager::class.java)
528+
whenever(mockContext.packageManager).thenReturn(pm)
529+
whenever(pm.resolveServiceAsUser(any(), anyInt(), anyInt())).thenReturn(null)
530+
531+
val unlockIntent =
532+
Intent(Intent.ACTION_USER_UNLOCKED).apply {
533+
putExtra(Intent.EXTRA_USER_HANDLE, context.userId)
534+
}
535+
536+
// When the user is unlocked, but does not have the component installed
537+
resumeListener.userChangeReceiver.onReceive(context, unlockIntent)
538+
539+
// Then we never attempt to connect to it
540+
verify(resumeBrowser, never()).findRecentMedia()
541+
}
542+
477543
/** Sets up mocks to successfully find a MBS that returns valid media. */
478544
private fun setUpMbsWithValidResolveInfo() {
479545
val pm = mock(PackageManager::class.java)
@@ -484,6 +550,8 @@ class MediaResumeListenerTest : SysuiTestCase() {
484550
resolveInfo.serviceInfo = serviceInfo
485551
resolveInfo.serviceInfo.name = CLASS_NAME
486552
val resumeInfo = listOf(resolveInfo)
487-
whenever(pm.queryIntentServices(any(), anyInt())).thenReturn(resumeInfo)
553+
whenever(pm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(resumeInfo)
554+
whenever(pm.resolveServiceAsUser(any(), anyInt(), anyInt())).thenReturn(resolveInfo)
555+
whenever(pm.getApplicationLabel(any())).thenReturn(PACKAGE_NAME)
488556
}
489557
}

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

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

@@ -396,8 +397,9 @@ public class ResumeMediaBrowserTest : SysuiTestCase() {
396397
componentName: ComponentName,
397398
browserFactory: MediaBrowserFactory,
398399
logger: ResumeMediaBrowserLogger,
399-
private val fakeController: MediaController
400-
) : ResumeMediaBrowser(context, callback, componentName, browserFactory, logger) {
400+
private val fakeController: MediaController,
401+
userId: Int
402+
) : ResumeMediaBrowser(context, callback, componentName, browserFactory, logger, userId) {
401403

402404
override fun createMediaController(token: MediaSession.Token): MediaController {
403405
return fakeController

0 commit comments

Comments
 (0)