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

Commit cad37e09 authored by Michael Mikhail's avatar Michael Mikhail Committed by Android (Google) Code Review
Browse files

Merge "Reuse created media drawables" into main

parents 925d1a6f 25cd7b6b
Loading
Loading
Loading
Loading
+10 −10
Original line number Diff line number Diff line
@@ -73,6 +73,7 @@ import com.android.systemui.media.controls.data.repository.MediaDataRepository
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager.Companion.isMediaNotification
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser
import com.android.systemui.media.controls.shared.MediaControlDrawables
import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_SOURCE
import com.android.systemui.media.controls.shared.model.EXTRA_VALUE_TRIGGER_PERIODIC
import com.android.systemui.media.controls.shared.model.MediaAction
@@ -1043,14 +1044,13 @@ class MediaDataProcessor(
        val playOrPause =
            if (isConnectingState(state.state)) {
                // Spinner needs to be animating to render anything. Start it here.
                val drawable =
                    context.getDrawable(com.android.internal.R.drawable.progress_small_material)
                val drawable = MediaControlDrawables.getProgress(context)
                (drawable as Animatable).start()
                MediaAction(
                    drawable,
                    null, // no action to perform when clicked
                    context.getString(R.string.controls_media_button_connecting),
                    context.getDrawable(R.drawable.ic_media_connecting_container),
                    MediaControlDrawables.getConnecting(context),
                    // Specify a rebind id to prevent the spinner from restarting on later binds.
                    com.android.internal.R.drawable.progress_small_material
                )
@@ -1143,23 +1143,23 @@ class MediaDataProcessor(
        return when (action) {
            PlaybackState.ACTION_PLAY -> {
                MediaAction(
                    context.getDrawable(R.drawable.ic_media_play),
                    MediaControlDrawables.getPlayIcon(context),
                    { controller.transportControls.play() },
                    context.getString(R.string.controls_media_button_play),
                    context.getDrawable(R.drawable.ic_media_play_container)
                    MediaControlDrawables.getPlayBackground(context)
                )
            }
            PlaybackState.ACTION_PAUSE -> {
                MediaAction(
                    context.getDrawable(R.drawable.ic_media_pause),
                    MediaControlDrawables.getPauseIcon(context),
                    { controller.transportControls.pause() },
                    context.getString(R.string.controls_media_button_pause),
                    context.getDrawable(R.drawable.ic_media_pause_container)
                    MediaControlDrawables.getPauseBackground(context)
                )
            }
            PlaybackState.ACTION_SKIP_TO_PREVIOUS -> {
                MediaAction(
                    context.getDrawable(R.drawable.ic_media_prev),
                    MediaControlDrawables.getPrevIcon(context),
                    { controller.transportControls.skipToPrevious() },
                    context.getString(R.string.controls_media_button_prev),
                    null
@@ -1167,7 +1167,7 @@ class MediaDataProcessor(
            }
            PlaybackState.ACTION_SKIP_TO_NEXT -> {
                MediaAction(
                    context.getDrawable(R.drawable.ic_media_next),
                    MediaControlDrawables.getNextIcon(context),
                    { controller.transportControls.skipToNext() },
                    context.getString(R.string.controls_media_button_next),
                    null
@@ -1308,7 +1308,7 @@ class MediaDataProcessor(
                .loadDrawable(context),
            action,
            context.getString(R.string.controls_media_resume),
            context.getDrawable(R.drawable.ic_media_play_container)
            MediaControlDrawables.getPlayBackground(context)
        )
    }

+23 −19
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import com.android.settingslib.media.PhoneMediaDevice
import com.android.settingslib.media.flags.Flags
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.controls.shared.MediaControlDrawables
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.MediaDeviceData
import com.android.systemui.media.controls.util.LocalMediaManagerFactory
@@ -142,6 +143,7 @@ constructor(
    interface Listener {
        /** Called when the route has changed for a given notification. */
        fun onMediaDeviceChanged(key: String, oldKey: String?, data: MediaDeviceData?)

        /** Called when the notification was removed. */
        fun onKeyRemoved(key: String, userInitiated: Boolean)
    }
@@ -159,6 +161,7 @@ constructor(

        val token
            get() = controller?.sessionToken

        private var started = false
        private var playbackType = PLAYBACK_TYPE_UNKNOWN
        private var playbackVolumeControlId: String? = null
@@ -170,6 +173,7 @@ constructor(
                    fgExecutor.execute { processDevice(key, oldKey, value) }
                }
            }

        // A device that is not yet connected but is expected to connect imminently. Because it's
        // expected to connect imminently, it should be displayed as the current device.
        private var aboutToConnectDeviceOverride: AboutToConnectDevice? = null
@@ -354,9 +358,9 @@ constructor(

                    activeDevice =
                        routingSession?.let {
                            val icon = if (it.selectedRoutes.size > 1) {
                                context.getDrawable(
                                        com.android.settingslib.R.drawable.ic_media_group_device)
                            val icon =
                                if (it.selectedRoutes.size > 1) {
                                    MediaControlDrawables.getGroupDevice(context)
                                } else {
                                    connectedDevice?.icon // Single route. We don't change the icon.
                                }
@@ -368,10 +372,12 @@ constructor(
                            //           route.
                            connectedDevice?.copy(
                                name = it.name ?: connectedDevice.name,
                                    icon = icon)
                        } ?: MediaDeviceData(
                                icon = icon
                            )
                        }
                            ?: MediaDeviceData(
                                enabled = false,
                            icon = context.getDrawable(R.drawable.ic_media_home_devices),
                                icon = MediaControlDrawables.getHomeDevices(context),
                                name = context.getString(R.string.media_seamless_other_device),
                                showBroadcastButton = false
                            )
@@ -434,10 +440,7 @@ constructor(
            return if (enableLeAudioSharing()) {
                MediaDeviceData(
                    enabled = false,
                    icon =
                        context.getDrawable(
                            com.android.settingslib.R.drawable.ic_bt_le_audio_sharing
                        ),
                    icon = MediaControlDrawables.getLeAudioSharing(context),
                    name = context.getString(R.string.audio_sharing_description),
                    intent = null,
                    showBroadcastButton = false
@@ -445,13 +448,14 @@ constructor(
            } else {
                MediaDeviceData(
                    enabled = true,
                    icon = context.getDrawable(R.drawable.settings_input_antenna),
                    icon = MediaControlDrawables.getAntenna(context),
                    name = broadcastDescription,
                    intent = null,
                    showBroadcastButton = true
                )
            }
        }

        /** Return a display name for the current device / route, or null if not possible */
        private fun getDeviceName(
            device: MediaDevice?,
+58 −47
Original line number Diff line number Diff line
@@ -45,128 +45,139 @@ object MediaControlDrawables {
    private var solid: Drawable? = null

    fun getProgress(context: Context): Drawable? {
        return progress
        if (!mediaControlsDrawablesReuse()) {
            return context.getDrawable(com.android.internal.R.drawable.progress_small_material)
        }
        return progress?.mutate()
            ?: context.getDrawable(com.android.internal.R.drawable.progress_small_material).also {
                if (!mediaControlsDrawablesReuse()) return@also
                progress = it
            }
    }

    fun getConnecting(context: Context): Drawable? {
        return connecting
        if (!mediaControlsDrawablesReuse()) {
            return context.getDrawable(R.drawable.ic_media_connecting_container)
        }
        return connecting?.mutate()
            ?: context.getDrawable(R.drawable.ic_media_connecting_container).also {
                if (!mediaControlsDrawablesReuse()) return@also
                connecting = it
            }
    }

    fun getPlayIcon(context: Context): AnimatedVectorDrawable? {
        if (!mediaControlsDrawablesReuse()) {
            return context.getDrawable(R.drawable.ic_media_play) as AnimatedVectorDrawable?
        }
        return playIcon?.let {
            it.reset()
            it
            it.mutate() as AnimatedVectorDrawable
        }
            ?: (context.getDrawable(R.drawable.ic_media_play) as AnimatedVectorDrawable?).also {
                if (!mediaControlsDrawablesReuse()) return@also
                playIcon = it
            }
    }

    fun getPlayBackground(context: Context): AnimatedVectorDrawable? {
        if (!mediaControlsDrawablesReuse()) {
            return context.getDrawable(R.drawable.ic_media_play_container)
                as AnimatedVectorDrawable?
        }
        return playBackground?.let {
            it.reset()
            it
            it.mutate() as AnimatedVectorDrawable
        }
            ?: (context.getDrawable(R.drawable.ic_media_play_container) as AnimatedVectorDrawable?)
                .also {
                    if (!mediaControlsDrawablesReuse()) return@also
                    playBackground = it
                }
                .also { playBackground = it }
    }

    fun getPauseIcon(context: Context): AnimatedVectorDrawable? {
        if (!mediaControlsDrawablesReuse()) {
            return context.getDrawable(R.drawable.ic_media_pause) as AnimatedVectorDrawable?
        }
        return pauseIcon?.let {
            it.reset()
            it
            it.mutate() as AnimatedVectorDrawable
        }
            ?: (context.getDrawable(R.drawable.ic_media_pause) as AnimatedVectorDrawable?).also {
                if (!mediaControlsDrawablesReuse()) return@also
                pauseIcon = it
            }
    }

    fun getPauseBackground(context: Context): AnimatedVectorDrawable? {
        if (!mediaControlsDrawablesReuse()) {
            return context.getDrawable(R.drawable.ic_media_pause_container)
                as AnimatedVectorDrawable?
        }
        return pauseBackground?.let {
            it.reset()
            it
            it.mutate() as AnimatedVectorDrawable
        }
            ?: (context.getDrawable(R.drawable.ic_media_pause_container) as AnimatedVectorDrawable?)
                .also {
                    if (!mediaControlsDrawablesReuse()) return@also
                    pauseBackground = it
                }
                .also { pauseBackground = it }
    }

    fun getNextIcon(context: Context): Drawable? {
        return nextIcon
            ?: context.getDrawable(R.drawable.ic_media_next).also {
                if (!mediaControlsDrawablesReuse()) return@also
                nextIcon = it
        if (!mediaControlsDrawablesReuse()) {
            return context.getDrawable(R.drawable.ic_media_next)
        }
        return nextIcon ?: context.getDrawable(R.drawable.ic_media_next).also { nextIcon = it }
    }

    fun getPrevIcon(context: Context): Drawable? {
        return prevIcon
            ?: context.getDrawable(R.drawable.ic_media_prev).also {
                if (!mediaControlsDrawablesReuse()) return@also
                prevIcon = it
        if (!mediaControlsDrawablesReuse()) {
            return context.getDrawable(R.drawable.ic_media_prev)
        }
        return prevIcon ?: context.getDrawable(R.drawable.ic_media_prev).also { prevIcon = it }
    }

    fun getLeAudioSharing(context: Context): Drawable? {
        if (!mediaControlsDrawablesReuse()) {
            return context.getDrawable(com.android.settingslib.R.drawable.ic_bt_le_audio_sharing)
        }
        return leAudioSharing
            ?: context.getDrawable(com.android.settingslib.R.drawable.ic_bt_le_audio_sharing).also {
                if (!mediaControlsDrawablesReuse()) return@also
                leAudioSharing = it
            }
    }

    fun getAntenna(context: Context): Drawable? {
        return antenna
            ?: context.getDrawable(R.drawable.settings_input_antenna).also {
                if (!mediaControlsDrawablesReuse()) return@also
                antenna = it
        if (!mediaControlsDrawablesReuse()) {
            return context.getDrawable(R.drawable.settings_input_antenna)
        }
        return antenna
            ?: context.getDrawable(R.drawable.settings_input_antenna).also { antenna = it }
    }

    fun getGroupDevice(context: Context): Drawable? {
        if (!mediaControlsDrawablesReuse()) {
            return context.getDrawable(com.android.settingslib.R.drawable.ic_media_group_device)
        }
        return groupDevice
            ?: context.getDrawable(com.android.settingslib.R.drawable.ic_media_group_device).also {
                if (!mediaControlsDrawablesReuse()) return@also
                groupDevice = it
            }
    }

    fun getHomeDevices(context: Context): Drawable? {
        return homeDevices
            ?: context.getDrawable(R.drawable.ic_media_home_devices).also {
                if (!mediaControlsDrawablesReuse()) return@also
                homeDevices = it
        if (!mediaControlsDrawablesReuse()) {
            return context.getDrawable(R.drawable.ic_media_home_devices)
        }
        return homeDevices
            ?: context.getDrawable(R.drawable.ic_media_home_devices).also { homeDevices = it }
    }

    fun getOutline(context: Context): Drawable? {
        return outline
            ?: context.getDrawable(R.drawable.qs_media_outline_button).also {
                if (!mediaControlsDrawablesReuse()) return@also
                outline = it
        if (!mediaControlsDrawablesReuse()) {
            return context.getDrawable(R.drawable.qs_media_outline_button)
        }
        return outline
            ?: context.getDrawable(R.drawable.qs_media_outline_button).also { outline = it }
    }

    fun getSolid(context: Context): Drawable? {
        return solid
            ?: context.getDrawable(R.drawable.qs_media_solid_button).also {
                if (!mediaControlsDrawablesReuse()) return@also
                solid = it
        if (!mediaControlsDrawablesReuse()) {
            return context.getDrawable(R.drawable.qs_media_solid_button)
        }
        return solid ?: context.getDrawable(R.drawable.qs_media_solid_button).also { solid = it }
    }
}
+3 −2
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaControlInteractor
import com.android.systemui.media.controls.shared.MediaControlDrawables
import com.android.systemui.media.controls.shared.model.MediaAction
import com.android.systemui.media.controls.shared.model.MediaButton
import com.android.systemui.media.controls.shared.model.MediaControlModel
@@ -284,9 +285,9 @@ class MediaControlViewModel(
            },
            cancelTextBackground =
                if (model.isDismissible) {
                    applicationContext.getDrawable(R.drawable.qs_media_outline_button)
                    MediaControlDrawables.getOutline(applicationContext)
                } else {
                    applicationContext.getDrawable(R.drawable.qs_media_solid_button)
                    MediaControlDrawables.getSolid(applicationContext)
                },
            onSettingsClicked = {
                logger.logLongPressSettings(model.uid, model.packageName, model.instanceId)
+90 −1
Original line number Diff line number Diff line
@@ -38,6 +38,9 @@ import android.media.session.MediaSession
import android.media.session.PlaybackState
import android.net.Uri
import android.os.Bundle
import android.os.UserHandle
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import android.service.notification.StatusBarNotification
import android.testing.TestableLooper
@@ -48,10 +51,13 @@ import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.Flags
import com.android.systemui.InstanceIdSequenceFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.dump.DumpManager
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.data.repository.MediaDataRepository
import com.android.systemui.media.controls.data.repository.MediaFilterRepository
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
@@ -69,6 +75,7 @@ import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
import com.android.systemui.statusbar.SbnBuilder
import com.android.systemui.statusbar.notificationLockscreenUserManager
import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.settings.FakeSettings
@@ -79,6 +86,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Rule
@@ -146,7 +154,6 @@ class MediaDataProcessorTest : SysuiTestCase() {
    @Mock lateinit var mediaSessionBasedFilter: MediaSessionBasedFilter
    @Mock lateinit var mediaDeviceManager: MediaDeviceManager
    @Mock lateinit var mediaDataCombineLatest: MediaDataCombineLatest
    @Mock lateinit var mediaDataFilter: MediaDataFilterImpl
    @Mock lateinit var listener: MediaDataManager.Listener
    @Mock lateinit var pendingIntent: PendingIntent
    @Mock lateinit var activityStarter: ActivityStarter
@@ -185,7 +192,9 @@ class MediaDataProcessorTest : SysuiTestCase() {
            Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
            1
        )
    private val notificationLockscreenUserManager = kosmos.notificationLockscreenUserManager
    private val mediaFilterRepository: MediaFilterRepository = kosmos.mediaFilterRepository
    private val mediaDataFilter: MediaDataFilterImpl = kosmos.mediaDataFilter

    private lateinit var staticMockSession: MockitoSession

@@ -258,6 +267,7 @@ class MediaDataProcessorTest : SysuiTestCase() {
        session = MediaSession(context, "MediaDataProcessorTestSession")
        mediaNotification =
            SbnBuilder().run {
                setUser(UserHandle(USER_ID))
                setPkg(PACKAGE_NAME)
                modifyNotification(context).also {
                    it.setSmallIcon(android.R.drawable.ic_media_pause)
@@ -1797,6 +1807,85 @@ class MediaDataProcessorTest : SysuiTestCase() {
            .isEqualTo(context.getString(R.string.controls_media_button_connecting))
    }

    @Test
    @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE)
    fun postWithPlaybackActions_drawablesReused() =
        kosmos.testScope.runTest {
            whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
            whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
            whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
            val stateActions =
                PlaybackState.ACTION_PAUSE or
                    PlaybackState.ACTION_SKIP_TO_PREVIOUS or
                    PlaybackState.ACTION_SKIP_TO_NEXT
            val stateBuilder =
                PlaybackState.Builder()
                    .setState(PlaybackState.STATE_PLAYING, 0, 10f)
                    .setActions(stateActions)
            whenever(controller.playbackState).thenReturn(stateBuilder.build())
            val userEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)

            mediaDataProcessor.addInternalListener(mediaDataFilter)
            mediaDataFilter.mediaDataProcessor = mediaDataProcessor
            addNotificationAndLoad()

            assertThat(userEntries).hasSize(1)
            val firstSemanticActions = userEntries?.values?.toList()?.get(0)?.semanticActions!!

            addNotificationAndLoad()

            assertThat(userEntries).hasSize(1)
            val secondSemanticActions = userEntries?.values?.toList()?.get(0)?.semanticActions!!
            assertThat(secondSemanticActions.playOrPause?.icon)
                .isEqualTo(firstSemanticActions.playOrPause?.icon)
            assertThat(secondSemanticActions.playOrPause?.background)
                .isEqualTo(firstSemanticActions.playOrPause?.background)
            assertThat(secondSemanticActions.nextOrCustom?.icon)
                .isEqualTo(firstSemanticActions.nextOrCustom?.icon)
            assertThat(secondSemanticActions.prevOrCustom?.icon)
                .isEqualTo(firstSemanticActions.prevOrCustom?.icon)
        }

    @Test
    @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE)
    fun postWithPlaybackActions_drawablesNotReused() =
        kosmos.testScope.runTest {
            whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
            whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
            whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
            val stateActions =
                PlaybackState.ACTION_PAUSE or
                    PlaybackState.ACTION_SKIP_TO_PREVIOUS or
                    PlaybackState.ACTION_SKIP_TO_NEXT
            val stateBuilder =
                PlaybackState.Builder()
                    .setState(PlaybackState.STATE_PLAYING, 0, 10f)
                    .setActions(stateActions)
            whenever(controller.playbackState).thenReturn(stateBuilder.build())
            val userEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)

            mediaDataProcessor.addInternalListener(mediaDataFilter)
            mediaDataFilter.mediaDataProcessor = mediaDataProcessor
            addNotificationAndLoad()

            assertThat(userEntries).hasSize(1)
            val firstSemanticActions = userEntries?.values?.toList()?.get(0)?.semanticActions!!

            addNotificationAndLoad()

            assertThat(userEntries).hasSize(1)
            val secondSemanticActions = userEntries?.values?.toList()?.get(0)?.semanticActions!!

            assertThat(secondSemanticActions.playOrPause?.icon)
                .isNotEqualTo(firstSemanticActions.playOrPause?.icon)
            assertThat(secondSemanticActions.playOrPause?.background)
                .isNotEqualTo(firstSemanticActions.playOrPause?.background)
            assertThat(secondSemanticActions.nextOrCustom?.icon)
                .isNotEqualTo(firstSemanticActions.nextOrCustom?.icon)
            assertThat(secondSemanticActions.prevOrCustom?.icon)
                .isNotEqualTo(firstSemanticActions.prevOrCustom?.icon)
        }

    @Test
    fun testPlaybackActions_reservedSpace() {
        val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
Loading