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

Commit af3ca453 authored by Michael Mikhail's avatar Michael Mikhail
Browse files

Send media controller calls to background thread

This CL makes sure we don't create playback state action button on main
thread.

Flag: EXEMPT BUGFIX
Bug: 345304469
Bug: 364610391
Test: atest MediaDataProcessorTest
Test: atest LegacyMediaDataManagerImplTest
Change-Id: Ia0a4fabede4484fab91fb48911acb89660349780
parent 15377e9b
Loading
Loading
Loading
Loading
+58 −59
Original line number Diff line number Diff line
@@ -111,7 +111,7 @@ private val ART_URIS =
    arrayOf(
        MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
        MediaMetadata.METADATA_KEY_ART_URI,
        MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI
        MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI,
    )

private const val TAG = "MediaDataManager"
@@ -136,7 +136,7 @@ private val LOADING =
        active = true,
        resumeAction = null,
        instanceId = InstanceId.fakeInstanceId(-1),
        appUid = Process.INVALID_UID
        appUid = Process.INVALID_UID,
    )

internal val EMPTY_SMARTSPACE_MEDIA_DATA =
@@ -163,7 +163,7 @@ private fun allowMediaRecommendations(context: Context): Boolean {
        Settings.Secure.getInt(
            context.contentResolver,
            Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
            1
            1,
        )
    return Utils.useQsMediaPlayer(context) && flag > 0
}
@@ -217,7 +217,7 @@ class LegacyMediaDataManagerImpl(
    private val themeText =
        com.android.settingslib.Utils.getColorAttr(
                context,
                com.android.internal.R.attr.textColorPrimary
                com.android.internal.R.attr.textColorPrimary,
            )
            .defaultColor

@@ -387,7 +387,7 @@ class LegacyMediaDataManagerImpl(
                uiExecutor,
                SmartspaceSession.OnTargetsAvailableListener { targets ->
                    smartspaceMediaDataProvider.onTargetsAvailable(targets)
                }
                },
            )
        }
        smartspaceSession?.let { it.requestSmartspaceUpdate() }
@@ -398,12 +398,12 @@ class LegacyMediaDataManagerImpl(
                    if (!allowMediaRecommendations) {
                        dismissSmartspaceRecommendation(
                            key = smartspaceMediaData.targetId,
                            delay = 0L
                            delay = 0L,
                        )
                    }
                }
            },
            Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION
            Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
        )
    }

@@ -461,7 +461,7 @@ class LegacyMediaDataManagerImpl(
        token: MediaSession.Token,
        appName: String,
        appIntent: PendingIntent,
        packageName: String
        packageName: String,
    ) {
        // Resume controls don't have a notification key, so store by package name instead
        if (!mediaEntries.containsKey(packageName)) {
@@ -497,7 +497,7 @@ class LegacyMediaDataManagerImpl(
                    token,
                    appName,
                    appIntent,
                    packageName
                    packageName,
                )
            }
        } else {
@@ -509,7 +509,7 @@ class LegacyMediaDataManagerImpl(
                    token,
                    appName,
                    appIntent,
                    packageName
                    packageName,
                )
            }
        }
@@ -609,14 +609,14 @@ class LegacyMediaDataManagerImpl(
                    result.appUid,
                    sbn.packageName,
                    instanceId,
                    result.playbackLocation
                    result.playbackLocation,
                )
            } else if (result.playbackLocation != currentEntry?.playbackLocation) {
                logger.logPlaybackLocationChange(
                    result.appUid,
                    sbn.packageName,
                    instanceId,
                    result.playbackLocation
                    result.playbackLocation,
                )
            }

@@ -722,16 +722,17 @@ class LegacyMediaDataManagerImpl(
    /** Called when the player's [PlaybackState] has been updated with new actions and/or state */
    private fun updateState(key: String, state: PlaybackState) {
        mediaEntries.get(key)?.let {
            backgroundExecutor.execute {
                val token = it.token
                if (token == null) {
                    if (DEBUG) Log.d(TAG, "State updated, but token was null")
                return
                    return@execute
                }
                val actions =
                    createActionsFromState(
                        it.packageName,
                        mediaControllerFactory.create(it.token),
                    UserHandle(it.userId)
                        UserHandle(it.userId),
                    )

                // Control buttons
@@ -745,7 +746,8 @@ class LegacyMediaDataManagerImpl(
                        it.copy(isPlaying = isPlayingState(state.state))
                    }
                if (DEBUG) Log.d(TAG, "State updated outside of notification")
            onMediaDataLoaded(key, key, data)
                foregroundExecutor.execute { onMediaDataLoaded(key, key, data) }
            }
        }
    }

@@ -773,7 +775,7 @@ class LegacyMediaDataManagerImpl(
        }
        foregroundExecutor.executeDelayed(
            { removeEntry(key = key, userInitiated = userInitiated) },
            delay
            delay,
        )
        return existed
    }
@@ -793,12 +795,12 @@ class LegacyMediaDataManagerImpl(
            smartspaceMediaData =
                EMPTY_SMARTSPACE_MEDIA_DATA.copy(
                    targetId = smartspaceMediaData.targetId,
                    instanceId = smartspaceMediaData.instanceId
                    instanceId = smartspaceMediaData.instanceId,
                )
        }
        foregroundExecutor.executeDelayed(
            { notifySmartspaceMediaDataRemoved(smartspaceMediaData.targetId, immediately = true) },
            delay
            delay,
        )
    }

@@ -826,7 +828,7 @@ class LegacyMediaDataManagerImpl(
        token: MediaSession.Token,
        appName: String,
        appIntent: PendingIntent,
        packageName: String
        packageName: String,
    ) =
        withContext(backgroundDispatcher) {
            val lastActive = systemClock.elapsedRealtime()
@@ -843,7 +845,7 @@ class LegacyMediaDataManagerImpl(
                        token,
                        appName,
                        appIntent,
                        packageName
                        packageName,
                    )
            if (result == null || desc.title.isNullOrBlank()) {
                Log.d(TAG, "No MediaData result for resumption")
@@ -882,7 +884,7 @@ class LegacyMediaDataManagerImpl(
                        appUid = result.appUid,
                        isExplicit = result.isExplicit,
                        resumeProgress = result.resumeProgress,
                    )
                    ),
                )
            }
        }
@@ -895,7 +897,7 @@ class LegacyMediaDataManagerImpl(
        token: MediaSession.Token,
        appName: String,
        appIntent: PendingIntent,
        packageName: String
        packageName: String,
    ) {
        if (desc.title.isNullOrBlank()) {
            Log.e(TAG, "Description incomplete")
@@ -966,7 +968,7 @@ class LegacyMediaDataManagerImpl(
                    appUid = appUid,
                    isExplicit = isExplicit,
                    resumeProgress = progress,
                )
                ),
            )
        }
    }
@@ -981,7 +983,7 @@ class LegacyMediaDataManagerImpl(
        val token =
            sbn.notification.extras.getParcelable(
                Notification.EXTRA_MEDIA_SESSION,
                MediaSession.Token::class.java
                MediaSession.Token::class.java,
            )
        if (token == null) {
            return
@@ -993,7 +995,7 @@ class LegacyMediaDataManagerImpl(
        val appInfo =
            notif.extras.getParcelable(
                Notification.EXTRA_BUILDER_APPLICATION_INFO,
                ApplicationInfo::class.java
                ApplicationInfo::class.java,
            ) ?: getAppInfoFromPackage(sbn.packageName)

        // App name
@@ -1057,7 +1059,7 @@ class LegacyMediaDataManagerImpl(
            val deviceIntent =
                extras.getParcelable(
                    Notification.EXTRA_MEDIA_REMOTE_INTENT,
                    PendingIntent::class.java
                    PendingIntent::class.java,
                )
            Log.d(TAG, "$key is RCN for $deviceName")

@@ -1073,7 +1075,7 @@ class LegacyMediaDataManagerImpl(
                        deviceDrawable,
                        deviceName,
                        deviceIntent,
                        showBroadcastButton = false
                        showBroadcastButton = false,
                    )
            }
        }
@@ -1160,7 +1162,7 @@ class LegacyMediaDataManagerImpl(
                mediaData.copy(
                    resumeAction = oldResumeAction,
                    hasCheckedForResume = oldHasCheckedForResume,
                    active = oldActive
                    active = oldActive,
                )
            onMediaDataLoaded(key, oldKey, mediaData)
        }
@@ -1169,7 +1171,7 @@ class LegacyMediaDataManagerImpl(
    private fun logSingleVsMultipleMediaAdded(
        appUid: Int,
        packageName: String,
        instanceId: InstanceId
        instanceId: InstanceId,
    ) {
        if (mediaEntries.size == 1) {
            logger.logSingleMediaPlayerInCarousel(appUid, packageName, instanceId)
@@ -1207,7 +1209,7 @@ class LegacyMediaDataManagerImpl(
    private fun createActionsFromState(
        packageName: String,
        controller: MediaController,
        user: UserHandle
        user: UserHandle,
    ): MediaButton? {
        if (!mediaFlags.areMediaSessionActionsEnabled(packageName, user)) {
            return null
@@ -1245,7 +1247,7 @@ class LegacyMediaDataManagerImpl(
                packageName,
                ContentProvider.getUriWithoutUserId(uri),
                Intent.FLAG_GRANT_READ_URI_PERMISSION,
                ContentProvider.getUserIdFromUri(uri, userId)
                ContentProvider.getUserIdFromUri(uri, userId),
            )
            return loadBitmapFromUri(uri)
        } catch (e: SecurityException) {
@@ -1282,7 +1284,7 @@ class LegacyMediaDataManagerImpl(
                val scale =
                    MediaDataUtils.getScaleFactor(
                        APair(width, height),
                        APair(artworkWidth, artworkHeight)
                        APair(artworkWidth, artworkHeight),
                    )

                // Downscale if needed
@@ -1307,7 +1309,7 @@ class LegacyMediaDataManagerImpl(
                .loadDrawable(context),
            action,
            context.getString(R.string.controls_media_resume),
            context.getDrawable(R.drawable.ic_media_play_container)
            context.getDrawable(R.drawable.ic_media_play_container),
        )
    }

@@ -1371,10 +1373,7 @@ class LegacyMediaDataManagerImpl(
                // There should NOT be more than 1 Smartspace media update. When it happens, it
                // indicates a bad state or an error. Reset the status accordingly.
                Log.wtf(TAG, "More than 1 Smartspace Media Update. Resetting the status...")
                notifySmartspaceMediaDataRemoved(
                    smartspaceMediaData.targetId,
                    immediately = false,
                )
                notifySmartspaceMediaDataRemoved(smartspaceMediaData.targetId, immediately = false)
                smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
            }
        }
@@ -1420,7 +1419,7 @@ class LegacyMediaDataManagerImpl(
    private fun handlePossibleRemoval(
        key: String,
        removed: MediaData,
        notificationRemoved: Boolean = false
        notificationRemoved: Boolean = false,
    ) {
        val hasSession = removed.token != null
        if (hasSession && removed.semanticActions != null) {
@@ -1445,7 +1444,7 @@ class LegacyMediaDataManagerImpl(
                Log.d(
                    TAG,
                    "Notification ($notificationRemoved) and/or session " +
                        "($hasSession) gone for inactive player $key"
                        "($hasSession) gone for inactive player $key",
                )
            }
            convertToResumePlayer(key, removed)
+18 −16
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.media.controls.domain.pipeline

import android.annotation.WorkerThread
import android.app.ActivityOptions
import android.app.BroadcastOptions
import android.app.Notification
@@ -50,6 +51,7 @@ private const val TAG = "MediaActions"
 * @return a Pair consisting of a list of media actions, and a list of ints representing which of
 *   those actions should be shown in the compact player
 */
@WorkerThread
fun createActionsFromState(
    context: Context,
    packageName: String,
@@ -69,7 +71,7 @@ fun createActionsFromState(
                context.getString(R.string.controls_media_button_connecting),
                context.getDrawable(R.drawable.ic_media_connecting_container),
                // Specify a rebind id to prevent the spinner from restarting on later binds.
                com.android.internal.R.drawable.progress_small_material
                com.android.internal.R.drawable.progress_small_material,
            )
        } else if (isPlayingState(state.state)) {
            getStandardAction(context, controller, state.actions, PlaybackState.ACTION_PAUSE)
@@ -128,7 +130,7 @@ fun createActionsFromState(
        nextCustomAction(),
        nextCustomAction(),
        reserveNext,
        reservePrev
        reservePrev,
    )
}

@@ -146,7 +148,7 @@ private fun getStandardAction(
    context: Context,
    controller: MediaController,
    stateActions: Long,
    @PlaybackState.Actions action: Long
    @PlaybackState.Actions action: Long,
): MediaAction? {
    if (!includesAction(stateActions, action)) {
        return null
@@ -158,7 +160,7 @@ private fun getStandardAction(
                context.getDrawable(R.drawable.ic_media_play),
                { controller.transportControls.play() },
                context.getString(R.string.controls_media_button_play),
                context.getDrawable(R.drawable.ic_media_play_container)
                context.getDrawable(R.drawable.ic_media_play_container),
            )
        }
        PlaybackState.ACTION_PAUSE -> {
@@ -166,7 +168,7 @@ private fun getStandardAction(
                context.getDrawable(R.drawable.ic_media_pause),
                { controller.transportControls.pause() },
                context.getString(R.string.controls_media_button_pause),
                context.getDrawable(R.drawable.ic_media_pause_container)
                context.getDrawable(R.drawable.ic_media_pause_container),
            )
        }
        PlaybackState.ACTION_SKIP_TO_PREVIOUS -> {
@@ -174,7 +176,7 @@ private fun getStandardAction(
                MediaControlDrawables.getPrevIcon(context),
                { controller.transportControls.skipToPrevious() },
                context.getString(R.string.controls_media_button_prev),
                null
                null,
            )
        }
        PlaybackState.ACTION_SKIP_TO_NEXT -> {
@@ -182,7 +184,7 @@ private fun getStandardAction(
                MediaControlDrawables.getNextIcon(context),
                { controller.transportControls.skipToNext() },
                context.getString(R.string.controls_media_button_next),
                null
                null,
            )
        }
        else -> null
@@ -194,13 +196,13 @@ private fun getCustomAction(
    context: Context,
    packageName: String,
    controller: MediaController,
    customAction: PlaybackState.CustomAction
    customAction: PlaybackState.CustomAction,
): MediaAction {
    return MediaAction(
        Icon.createWithResource(packageName, customAction.icon).loadDrawable(context),
        { controller.transportControls.sendCustomAction(customAction, customAction.extras) },
        customAction.name,
        null
        null,
    )
}

@@ -218,7 +220,7 @@ private fun includesAction(stateActions: Long, @PlaybackState.Actions action: Lo
/** Generate action buttons based on notification actions */
fun createActionsFromNotification(
    context: Context,
    sbn: StatusBarNotification
    sbn: StatusBarNotification,
): Pair<List<MediaNotificationAction>, List<Int>> {
    val notif = sbn.notification
    val actionIcons: MutableList<MediaNotificationAction> = ArrayList()
@@ -229,7 +231,7 @@ fun createActionsFromNotification(
    if (actionsToShowCollapsed.size > MAX_COMPACT_ACTIONS) {
        Log.e(
            TAG,
            "Too many compact actions for ${sbn.key}, limiting to first $MAX_COMPACT_ACTIONS"
            "Too many compact actions for ${sbn.key}, limiting to first $MAX_COMPACT_ACTIONS",
        )
        actionsToShowCollapsed = actionsToShowCollapsed.subList(0, MAX_COMPACT_ACTIONS)
    }
@@ -239,7 +241,7 @@ fun createActionsFromNotification(
            Log.w(
                TAG,
                "Too many notification actions for ${sbn.key}, " +
                    "limiting to first $MAX_NOTIFICATION_ACTIONS"
                    "limiting to first $MAX_NOTIFICATION_ACTIONS",
            )
        }

@@ -253,7 +255,7 @@ fun createActionsFromNotification(
            val themeText =
                com.android.settingslib.Utils.getColorAttr(
                        context,
                        com.android.internal.R.attr.textColorPrimary
                        com.android.internal.R.attr.textColorPrimary,
                    )
                    .defaultColor

@@ -271,7 +273,7 @@ fun createActionsFromNotification(
                    action.isAuthenticationRequired,
                    action.actionIntent,
                    mediaActionIcon,
                    action.title
                    action.title,
                )
            actionIcons.add(mediaAction)
        }
@@ -288,7 +290,7 @@ fun createActionsFromNotification(
 */
fun getNotificationActions(
    actions: List<MediaNotificationAction>,
    activityStarter: ActivityStarter
    activityStarter: ActivityStarter,
): List<MediaAction> {
    return actions.map { action ->
        val runnable =
@@ -303,7 +305,7 @@ fun getNotificationActions(
                            activityStarter.dismissKeyguardThenExecute(
                                { sendPendingIntent(action.actionIntent) },
                                {},
                                true
                                true,
                            )
                        else -> sendPendingIntent(actionIntent)
                    }
+60 −53

File changed.

Preview size limit exceeded, changes collapsed.

+12 −6
Original line number Diff line number Diff line
@@ -1890,7 +1890,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa

        // Callback gets an updated state
        val state = PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0L, 1f).build()
        stateCallbackCaptor.value.invoke(KEY, state)
        onStateUpdated(KEY, state)

        // Listener is notified of updated state
        verify(listener)
@@ -1911,7 +1911,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa

        // No media added with this key

        stateCallbackCaptor.value.invoke(KEY, state)
        onStateUpdated(KEY, state)
        verify(listener, never())
            .onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(), anyBoolean())
    }
@@ -1928,7 +1928,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa
        val state = PlaybackState.Builder().build()

        // Then no changes are made
        stateCallbackCaptor.value.invoke(KEY, state)
        onStateUpdated(KEY, state)
        verify(listener, never())
            .onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(), anyBoolean())
    }
@@ -1939,7 +1939,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa
        whenever(controller.playbackState).thenReturn(state)

        addNotificationAndLoad()
        stateCallbackCaptor.value.invoke(KEY, state)
        onStateUpdated(KEY, state)

        verify(listener)
            .onMediaDataLoaded(
@@ -1983,7 +1983,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa
            backgroundExecutor.runAllReady()
            foregroundExecutor.runAllReady()

            stateCallbackCaptor.value.invoke(PACKAGE_NAME, state)
            onStateUpdated(PACKAGE_NAME, state)

            verify(listener)
                .onMediaDataLoaded(
@@ -2008,7 +2008,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa
                .build()

        addNotificationAndLoad()
        stateCallbackCaptor.value.invoke(KEY, state)
        onStateUpdated(KEY, state)

        verify(listener)
            .onMediaDataLoaded(
@@ -2518,4 +2518,10 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa
                eq(false),
            )
    }

    private fun onStateUpdated(key: String, state: PlaybackState) {
        stateCallbackCaptor.value.invoke(key, state)
        backgroundExecutor.runAllReady()
        foregroundExecutor.runAllReady()
    }
}
+13 −6
Original line number Diff line number Diff line
@@ -1967,7 +1967,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {

        // Callback gets an updated state
        val state = PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0L, 1f).build()
        stateCallbackCaptor.value.invoke(KEY, state)
        testScope.onStateUpdated(KEY, state)

        // Listener is notified of updated state
        verify(listener)
@@ -1988,7 +1988,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {

        // No media added with this key

        stateCallbackCaptor.value.invoke(KEY, state)
        testScope.onStateUpdated(KEY, state)
        verify(listener, never())
            .onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(), anyBoolean())
    }
@@ -2005,7 +2005,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
        val state = PlaybackState.Builder().build()

        // Then no changes are made
        stateCallbackCaptor.value.invoke(KEY, state)
        testScope.onStateUpdated(KEY, state)
        verify(listener, never())
            .onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(), anyBoolean())
    }
@@ -2016,7 +2016,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
        whenever(controller.playbackState).thenReturn(state)

        addNotificationAndLoad()
        stateCallbackCaptor.value.invoke(KEY, state)
        testScope.onStateUpdated(KEY, state)

        verify(listener)
            .onMediaDataLoaded(
@@ -2059,7 +2059,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
        backgroundExecutor.runAllReady()
        foregroundExecutor.runAllReady()

        stateCallbackCaptor.value.invoke(PACKAGE_NAME, state)
        testScope.onStateUpdated(PACKAGE_NAME, state)

        verify(listener)
            .onMediaDataLoaded(
@@ -2084,7 +2084,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
                .build()

        addNotificationAndLoad()
        stateCallbackCaptor.value.invoke(KEY, state)
        testScope.onStateUpdated(KEY, state)

        verify(listener)
            .onMediaDataLoaded(
@@ -2603,4 +2603,11 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
                eq(false),
            )
    }

    /** Helper function to update state and run executors */
    private fun TestScope.onStateUpdated(key: String, state: PlaybackState) {
        stateCallbackCaptor.value.invoke(key, state)
        runCurrent()
        advanceUntilIdle()
    }
}