Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6ccd23c3 authored by Automerger Merge Worker's avatar Automerger Merge Worker
Browse files

Merge "Merge "Improve user handling when querying for resumable media" into...

Merge "Merge "Improve user handling when querying for resumable media" into udc-qpr-dev am: 706aef53" into udc-qpr-dev-plus-aosp am: dfebad3f

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/23785855



Change-Id: I042294b5487ce3386f06ddb92901b480f2792ebd
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents ad33bcc2 dfebad3f
Loading
Loading
Loading
Loading
+27 −9
Original line number Original line Diff line number Diff line
@@ -122,9 +122,9 @@ constructor(
                    Log.e(TAG, "Error getting package information", e)
                    Log.e(TAG, "Error getting package information", e)
                }
                }


                Log.d(TAG, "Adding resume controls $desc")
                Log.d(TAG, "Adding resume controls for ${browser.userId}: $desc")
                mediaDataManager.addResumptionControls(
                mediaDataManager.addResumptionControls(
                    currentUserId,
                    browser.userId,
                    desc,
                    desc,
                    resumeAction,
                    resumeAction,
                    token,
                    token,
@@ -196,7 +196,11 @@ constructor(
                }
                }
            resumeComponents.add(component to lastPlayed)
            resumeComponents.add(component to lastPlayed)
        }
        }
        Log.d(TAG, "loaded resume components ${resumeComponents.toArray().contentToString()}")
        Log.d(
            TAG,
            "loaded resume components for $currentUserId: " +
                "${resumeComponents.toArray().contentToString()}"
        )


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


        val pm = context.packageManager
        val now = systemClock.currentTimeMillis()
        val now = systemClock.currentTimeMillis()
        resumeComponents.forEach {
        resumeComponents.forEach {
            if (now.minus(it.second) <= RESUME_MEDIA_TIMEOUT) {
            if (now.minus(it.second) <= RESUME_MEDIA_TIMEOUT) {
                val browser = mediaBrowserFactory.create(mediaBrowserCallback, it.first)
                // Verify that the service exists for this user
                val intent = Intent(MediaBrowserService.SERVICE_INTERFACE)
                intent.component = it.first
                val inf = pm.resolveServiceAsUser(intent, 0, currentUserId)
                if (inf != null) {
                    val browser =
                        mediaBrowserFactory.create(mediaBrowserCallback, it.first, currentUserId)
                    browser.findRecentMedia()
                    browser.findRecentMedia()
                } else {
                    Log.d(TAG, "User $currentUserId does not have component ${it.first}")
                }
            }
            }
        }
        }
    }
    }
@@ -244,7 +258,7 @@ constructor(
                Log.d(TAG, "Checking for service component for " + data.packageName)
                Log.d(TAG, "Checking for service component for " + data.packageName)
                val pm = context.packageManager
                val pm = context.packageManager
                val serviceIntent = Intent(MediaBrowserService.SERVICE_INTERFACE)
                val serviceIntent = Intent(MediaBrowserService.SERVICE_INTERFACE)
                val resumeInfo = pm.queryIntentServices(serviceIntent, 0)
                val resumeInfo = pm.queryIntentServicesAsUser(serviceIntent, 0, currentUserId)


                val inf = resumeInfo?.filter { it.serviceInfo.packageName == data.packageName }
                val inf = resumeInfo?.filter { it.serviceInfo.packageName == data.packageName }
                if (inf != null && inf.size > 0) {
                if (inf != null && inf.size > 0) {
@@ -280,13 +294,17 @@ constructor(
                        browser: ResumeMediaBrowser
                        browser: ResumeMediaBrowser
                    ) {
                    ) {
                        // Since this is a test, just save the component for later
                        // Since this is a test, just save the component for later
                        Log.d(TAG, "Can get resumable media from $componentName")
                        Log.d(
                            TAG,
                            "Can get resumable media for ${browser.userId} from $componentName"
                        )
                        mediaDataManager.setResumeAction(key, getResumeAction(componentName))
                        mediaDataManager.setResumeAction(key, getResumeAction(componentName))
                        updateResumptionList(componentName)
                        updateResumptionList(componentName)
                        mediaBrowser = null
                        mediaBrowser = null
                    }
                    }
                },
                },
                componentName
                componentName,
                currentUserId
            )
            )
        mediaBrowser?.testConnection()
        mediaBrowser?.testConnection()
    }
    }
@@ -326,7 +344,7 @@ constructor(
    /** Get a runnable which will resume media playback */
    /** Get a runnable which will resume media playback */
    private fun getResumeAction(componentName: ComponentName): Runnable {
    private fun getResumeAction(componentName: ComponentName): Runnable {
        return Runnable {
        return Runnable {
            mediaBrowser = mediaBrowserFactory.create(null, componentName)
            mediaBrowser = mediaBrowserFactory.create(null, componentName, currentUserId)
            mediaBrowser?.restart()
            mediaBrowser?.restart()
        }
        }
    }
    }
+14 −1
Original line number Original line Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.media.controls.resume;
package com.android.systemui.media.controls.resume;


import android.annotation.Nullable;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.ComponentName;
import android.content.Context;
import android.content.Context;
@@ -53,6 +54,7 @@ public class ResumeMediaBrowser {
    private final ResumeMediaBrowserLogger mLogger;
    private final ResumeMediaBrowserLogger mLogger;
    private final ComponentName mComponentName;
    private final ComponentName mComponentName;
    private final MediaController.Callback mMediaControllerCallback = new SessionDestroyCallback();
    private final MediaController.Callback mMediaControllerCallback = new SessionDestroyCallback();
    @UserIdInt private final int mUserId;


    private MediaBrowser mMediaBrowser;
    private MediaBrowser mMediaBrowser;
    @Nullable private MediaController mMediaController;
    @Nullable private MediaController mMediaController;
@@ -62,18 +64,21 @@ public class ResumeMediaBrowser {
     * @param context the context
     * @param context the context
     * @param callback used to report media items found
     * @param callback used to report media items found
     * @param componentName Component name of the MediaBrowserService this browser will connect to
     * @param componentName Component name of the MediaBrowserService this browser will connect to
     * @param userId ID of the current user
     */
     */
    public ResumeMediaBrowser(
    public ResumeMediaBrowser(
            Context context,
            Context context,
            @Nullable Callback callback,
            @Nullable Callback callback,
            ComponentName componentName,
            ComponentName componentName,
            MediaBrowserFactory browserFactory,
            MediaBrowserFactory browserFactory,
            ResumeMediaBrowserLogger logger) {
            ResumeMediaBrowserLogger logger,
            @UserIdInt int userId) {
        mContext = context;
        mContext = context;
        mCallback = callback;
        mCallback = callback;
        mComponentName = componentName;
        mComponentName = componentName;
        mBrowserFactory = browserFactory;
        mBrowserFactory = browserFactory;
        mLogger = logger;
        mLogger = logger;
        mUserId = userId;
    }
    }


    /**
    /**
@@ -284,6 +289,14 @@ public class ResumeMediaBrowser {
        return new MediaController(mContext, token);
        return new MediaController(mContext, token);
    }
    }


    /**
     * Get the ID of the user associated with this broswer
     * @return the user ID
     */
    public @UserIdInt int getUserId() {
        return mUserId;
    }

    /**
    /**
     * Get the media session token
     * Get the media session token
     * @return the token, or null if the MediaBrowser is null or disconnected
     * @return the token, or null if the MediaBrowser is null or disconnected
+5 −2
Original line number Original line Diff line number Diff line
@@ -16,6 +16,7 @@


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


import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.ComponentName;
import android.content.Context;
import android.content.Context;


@@ -42,10 +43,12 @@ public class ResumeMediaBrowserFactory {
     *
     *
     * @param callback will be called on connection or error, and addTrack when media item found
     * @param callback will be called on connection or error, and addTrack when media item found
     * @param componentName component to browse
     * @param componentName component to browse
     * @param userId ID of the current user
     * @return
     * @return
     */
     */
    public ResumeMediaBrowser create(ResumeMediaBrowser.Callback callback,
    public ResumeMediaBrowser create(ResumeMediaBrowser.Callback callback,
            ComponentName componentName) {
            ComponentName componentName, @UserIdInt int userId) {
        return new ResumeMediaBrowser(mContext, callback, componentName, mBrowserFactory, mLogger);
        return new ResumeMediaBrowser(mContext, callback, componentName, mBrowserFactory, mLogger,
            userId);
    }
    }
}
}
+69 −3
Original line number Original line Diff line number Diff line
@@ -98,6 +98,8 @@ class MediaResumeListenerTest : SysuiTestCase() {
    @Captor lateinit var callbackCaptor: ArgumentCaptor<ResumeMediaBrowser.Callback>
    @Captor lateinit var callbackCaptor: ArgumentCaptor<ResumeMediaBrowser.Callback>
    @Captor lateinit var actionCaptor: ArgumentCaptor<Runnable>
    @Captor lateinit var actionCaptor: ArgumentCaptor<Runnable>
    @Captor lateinit var componentCaptor: ArgumentCaptor<String>
    @Captor lateinit var componentCaptor: ArgumentCaptor<String>
    @Captor lateinit var userIdCaptor: ArgumentCaptor<Int>
    @Captor lateinit var userCallbackCaptor: ArgumentCaptor<UserTracker.Callback>


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


        whenever(resumeBrowserFactory.create(capture(callbackCaptor), any()))
        whenever(resumeBrowserFactory.create(capture(callbackCaptor), any(), capture(userIdCaptor)))
            .thenReturn(resumeBrowser)
            .thenReturn(resumeBrowser)


        // resume components are stored in sharedpreferences
        // resume components are stored in sharedpreferences
@@ -334,6 +336,7 @@ class MediaResumeListenerTest : SysuiTestCase() {
    @Test
    @Test
    fun testOnUserUnlock_loadsTracks() {
    fun testOnUserUnlock_loadsTracks() {
        // Set up mock service to successfully find valid media
        // Set up mock service to successfully find valid media
        setUpMbsWithValidResolveInfo()
        val description = MediaDescription.Builder().setTitle(TITLE).build()
        val description = MediaDescription.Builder().setTitle(TITLE).build()
        val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
        val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
        whenever(resumeBrowser.token).thenReturn(token)
        whenever(resumeBrowser.token).thenReturn(token)
@@ -417,6 +420,7 @@ class MediaResumeListenerTest : SysuiTestCase() {
    @Test
    @Test
    fun testLoadComponents_recentlyPlayed_adds() {
    fun testLoadComponents_recentlyPlayed_adds() {
        // Set up browser to return successfully
        // Set up browser to return successfully
        setUpMbsWithValidResolveInfo()
        val description = MediaDescription.Builder().setTitle(TITLE).build()
        val description = MediaDescription.Builder().setTitle(TITLE).build()
        val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
        val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
        whenever(resumeBrowser.token).thenReturn(token)
        whenever(resumeBrowser.token).thenReturn(token)
@@ -600,7 +604,7 @@ class MediaResumeListenerTest : SysuiTestCase() {


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


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


    @Test
    fun testUserUnlocked_userChangeWhileQuerying() {
        val firstUserId = 1
        val secondUserId = 2
        val description = MediaDescription.Builder().setTitle(TITLE).build()
        val component = ComponentName(PACKAGE_NAME, CLASS_NAME)

        setUpMbsWithValidResolveInfo()
        whenever(resumeBrowser.token).thenReturn(token)
        whenever(resumeBrowser.appIntent).thenReturn(pendingIntent)

        val unlockIntent =
            Intent(Intent.ACTION_USER_UNLOCKED).apply {
                putExtra(Intent.EXTRA_USER_HANDLE, firstUserId)
            }
        verify(userTracker).addCallback(capture(userCallbackCaptor), any())

        // When the first user unlocks and we query their recent media
        userCallbackCaptor.value.onUserChanged(firstUserId, context)
        resumeListener.userUnlockReceiver.onReceive(context, unlockIntent)
        whenever(resumeBrowser.userId).thenReturn(userIdCaptor.value)
        verify(resumeBrowser, times(3)).findRecentMedia()

        // And the user changes before the MBS response is received
        userCallbackCaptor.value.onUserChanged(secondUserId, context)
        callbackCaptor.value.addTrack(description, component, resumeBrowser)

        // Then the loaded media is correctly associated with the first user
        verify(mediaDataManager)
            .addResumptionControls(
                eq(firstUserId),
                eq(description),
                any(),
                eq(token),
                eq(PACKAGE_NAME),
                eq(pendingIntent),
                eq(PACKAGE_NAME)
            )
    }

    @Test
    fun testUserUnlocked_noComponent_doesNotQuery() {
        // Set up a valid MBS, but user does not have the service available
        setUpMbsWithValidResolveInfo()
        val pm = mock(PackageManager::class.java)
        whenever(mockContext.packageManager).thenReturn(pm)
        whenever(pm.resolveServiceAsUser(any(), anyInt(), anyInt())).thenReturn(null)

        val unlockIntent =
            Intent(Intent.ACTION_USER_UNLOCKED).apply {
                putExtra(Intent.EXTRA_USER_HANDLE, context.userId)
            }

        // When the user is unlocked, but does not have the component installed
        resumeListener.userUnlockReceiver.onReceive(context, unlockIntent)

        // Then we never attempt to connect to it
        verify(resumeBrowser, never()).findRecentMedia()
    }

    /** Sets up mocks to successfully find a MBS that returns valid media. */
    /** Sets up mocks to successfully find a MBS that returns valid media. */
    private fun setUpMbsWithValidResolveInfo() {
    private fun setUpMbsWithValidResolveInfo() {
        val pm = mock(PackageManager::class.java)
        val pm = mock(PackageManager::class.java)
@@ -620,6 +684,8 @@ class MediaResumeListenerTest : SysuiTestCase() {
        resolveInfo.serviceInfo = serviceInfo
        resolveInfo.serviceInfo = serviceInfo
        resolveInfo.serviceInfo.name = CLASS_NAME
        resolveInfo.serviceInfo.name = CLASS_NAME
        val resumeInfo = listOf(resolveInfo)
        val resumeInfo = listOf(resolveInfo)
        whenever(pm.queryIntentServices(any(), anyInt())).thenReturn(resumeInfo)
        whenever(pm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(resumeInfo)
        whenever(pm.resolveServiceAsUser(any(), anyInt(), anyInt())).thenReturn(resolveInfo)
        whenever(pm.getApplicationLabel(any())).thenReturn(PACKAGE_NAME)
    }
    }
}
}
+5 −3
Original line number Original line Diff line number Diff line
@@ -93,7 +93,8 @@ public class ResumeMediaBrowserTest : SysuiTestCase() {
                component,
                component,
                browserFactory,
                browserFactory,
                logger,
                logger,
                mediaController
                mediaController,
                context.userId,
            )
            )
    }
    }


@@ -381,8 +382,9 @@ public class ResumeMediaBrowserTest : SysuiTestCase() {
        componentName: ComponentName,
        componentName: ComponentName,
        browserFactory: MediaBrowserFactory,
        browserFactory: MediaBrowserFactory,
        logger: ResumeMediaBrowserLogger,
        logger: ResumeMediaBrowserLogger,
        private val fakeController: MediaController
        private val fakeController: MediaController,
    ) : ResumeMediaBrowser(context, callback, componentName, browserFactory, logger) {
        userId: Int,
    ) : ResumeMediaBrowser(context, callback, componentName, browserFactory, logger, userId) {


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