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

Commit 4e201835 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Improve user handling when querying for resumable media" into tm-dev

parents 1a3bef43 d6174128
Loading
Loading
Loading
Loading
+36 −10
Original line number Original line Diff line number Diff line
@@ -101,9 +101,16 @@ class MediaResumeListener @Inject 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(currentUserId, desc, resumeAction, token,
            mediaDataManager.addResumptionControls(
                appName.toString(), appIntent, component.packageName)
                browser.userId,
                desc,
                resumeAction,
                token,
                appName.toString(),
                appIntent,
                component.packageName
            )
        }
        }
    }
    }


@@ -158,7 +165,11 @@ class MediaResumeListener @Inject 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
@@ -174,11 +185,21 @@ class MediaResumeListener @Inject 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}")
                }
            }
            }
        }
        }
    }
    }
@@ -202,7 +223,7 @@ class MediaResumeListener @Inject 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 {
                val inf = resumeInfo?.filter {
                    it.serviceInfo.packageName == data.packageName
                    it.serviceInfo.packageName == data.packageName
@@ -244,13 +265,18 @@ class MediaResumeListener @Inject 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()
    }
    }


@@ -290,7 +316,7 @@ class MediaResumeListener @Inject constructor(
     */
     */
    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;
package com.android.systemui.media;


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;
    }
    }


    /**
    /**
@@ -275,6 +280,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;
package com.android.systemui.media;


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);
    }
    }
}
}
+71 −3
Original line number Original line Diff line number Diff line
@@ -87,6 +87,7 @@ 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>


    private lateinit var executor: FakeExecutor
    private lateinit var executor: FakeExecutor
    private lateinit var data: MediaData
    private lateinit var data: MediaData
@@ -107,7 +108,7 @@ class MediaResumeListenerTest : SysuiTestCase() {
        Settings.Secure.putInt(context.contentResolver,
        Settings.Secure.putInt(context.contentResolver,
            Settings.Secure.MEDIA_CONTROLS_RESUME, 1)
            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
@@ -118,6 +119,7 @@ class MediaResumeListenerTest : SysuiTestCase() {
        whenever(sharedPrefsEditor.putString(any(), any())).thenReturn(sharedPrefsEditor)
        whenever(sharedPrefsEditor.putString(any(), any())).thenReturn(sharedPrefsEditor)
        whenever(mockContext.packageManager).thenReturn(context.packageManager)
        whenever(mockContext.packageManager).thenReturn(context.packageManager)
        whenever(mockContext.contentResolver).thenReturn(context.contentResolver)
        whenever(mockContext.contentResolver).thenReturn(context.contentResolver)
        whenever(mockContext.userId).thenReturn(context.userId)


        executor = FakeExecutor(clock)
        executor = FakeExecutor(clock)
        resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
        resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
@@ -243,6 +245,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)
@@ -316,6 +319,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)
@@ -464,7 +468,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
@@ -474,6 +478,68 @@ class MediaResumeListenerTest : SysuiTestCase() {
        verify(resumeBrowser).disconnect()
        verify(resumeBrowser).disconnect()
    }
    }


    @Test
    fun testUserUnlocked_userChangeWhileQuerying() {
        val firstUserId = context.userId
        val secondUserId = firstUserId + 1
        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)
            }

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

        // And the user changes before the MBS response is received
        val changeIntent =
            Intent(Intent.ACTION_USER_SWITCHED).apply {
                putExtra(Intent.EXTRA_USER_HANDLE, secondUserId)
            }
        resumeListener.userChangeReceiver.onReceive(context, changeIntent)
        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.userChangeReceiver.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)
@@ -484,6 +550,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
@@ -92,7 +92,8 @@ public class ResumeMediaBrowserTest : SysuiTestCase() {
            component,
            component,
            browserFactory,
            browserFactory,
            logger,
            logger,
            mediaController
            mediaController,
            context.userId
        )
        )
    }
    }


@@ -396,8 +397,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