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

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

Merge "[Flexiglass] avoid UI collection of duplicate values" into main

parents e31c9036 fefedd24
Loading
Loading
Loading
Loading
+70 −24
Original line number Diff line number Diff line
@@ -24,26 +24,25 @@ import android.media.session.MediaSession
import android.media.session.PlaybackState
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.domain.pipeline.MediaDataFilterImpl
import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter
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.mediaInstanceId
import com.android.systemui.statusbar.notificationLockscreenUserManager
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers
import org.mockito.ArgumentMatchers.any
import org.mockito.Mockito
import org.mockito.kotlin.eq
import org.mockito.kotlin.whenever

@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -52,17 +51,15 @@ class MediaControlViewModelTest : SysuiTestCase() {
    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope

    private val mediaDataFilter: MediaDataFilterImpl = kosmos.mediaDataFilter
    private val mediaDataFilter = kosmos.mediaDataFilter
    private val notificationLockscreenUserManager = kosmos.notificationLockscreenUserManager
    private val packageManager = kosmos.packageManager
    private val drawable = context.getDrawable(R.drawable.ic_media_play)
    private val instanceId: InstanceId = kosmos.mediaInstanceId

    private val instanceId = kosmos.mediaInstanceId
    private val underTest: MediaControlViewModel = kosmos.mediaControlViewModel

    @Test
    fun addMediaControl_mediaControlViewModelIsLoaded() =
        testScope.runTest {
    @Before
    fun setUp() {
        whenever(packageManager.getApplicationIcon(Mockito.anyString())).thenReturn(drawable)
        whenever(packageManager.getApplicationIcon(any(ApplicationInfo::class.java)))
            .thenReturn(drawable)
@@ -71,11 +68,14 @@ class MediaControlViewModelTest : SysuiTestCase() {
        whenever(packageManager.getApplicationLabel(any())).thenReturn(PACKAGE_NAME)
        whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
        whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
            val playerModel by collectLastValue(underTest.player)

        context.setMockPackageManager(packageManager)
    }

            val mediaData = initMediaData()
    @Test
    fun addMediaControl_mediaControlViewModelIsLoaded() =
        testScope.runTest {
            val playerModel by collectLastValue(underTest.player)
            val mediaData = initMediaData(ARTIST, TITLE)

            mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData)

@@ -88,7 +88,51 @@ class MediaControlViewModelTest : SysuiTestCase() {
            assertThat(playerModel?.playTurbulenceNoise).isFalse()
        }

    private fun initMediaData(): MediaData {
    @Test
    fun emitDuplicateMediaControls_mediaControlIsNotBound() =
        testScope.runTest {
            val playerModel by collectLastValue(underTest.player)
            val mediaData = initMediaData(ARTIST, TITLE)

            mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData)

            assertThat(playerModel).isNotNull()
            assertThat(playerModel?.titleName).isEqualTo(TITLE)
            assertThat(playerModel?.artistName).isEqualTo(ARTIST)
            assertThat(underTest.isNewPlayer(playerModel!!)).isTrue()

            mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData)

            assertThat(playerModel).isNotNull()
            assertThat(playerModel?.titleName).isEqualTo(TITLE)
            assertThat(playerModel?.artistName).isEqualTo(ARTIST)
            assertThat(underTest.isNewPlayer(playerModel!!)).isFalse()
        }

    @Test
    fun emitDifferentMediaControls_mediaControlIsBound() =
        testScope.runTest {
            val playerModel by collectLastValue(underTest.player)
            var mediaData = initMediaData(ARTIST, TITLE)

            mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData)

            assertThat(playerModel).isNotNull()
            assertThat(playerModel?.titleName).isEqualTo(TITLE)
            assertThat(playerModel?.artistName).isEqualTo(ARTIST)
            assertThat(underTest.isNewPlayer(playerModel!!)).isTrue()

            mediaData = initMediaData(ARTIST_2, TITLE_2)

            mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData)

            assertThat(playerModel).isNotNull()
            assertThat(playerModel?.titleName).isEqualTo(TITLE_2)
            assertThat(playerModel?.artistName).isEqualTo(ARTIST_2)
            assertThat(underTest.isNewPlayer(playerModel!!)).isTrue()
        }

    private fun initMediaData(artist: String, title: String): MediaData {
        val device = MediaDeviceData(true, null, DEVICE_NAME, null, showBroadcastButton = true)

        // Create media session
@@ -111,12 +155,12 @@ class MediaControlViewModelTest : SysuiTestCase() {

        return MediaData(
            userId = USER_ID,
            artist = ARTIST,
            song = TITLE,
            artist = artist,
            song = title,
            packageName = PACKAGE,
            token = session.sessionToken,
            device = device,
            instanceId = instanceId
            instanceId = instanceId,
        )
    }

@@ -127,6 +171,8 @@ class MediaControlViewModelTest : SysuiTestCase() {
        private const val PACKAGE = "PKG"
        private const val ARTIST = "ARTIST"
        private const val TITLE = "TITLE"
        private const val ARTIST_2 = "ARTIST_2"
        private const val TITLE_2 = "TITLE_2"
        private const val DEVICE_NAME = "DEVICE_NAME"
        private const val SESSION_KEY = "SESSION_KEY"
        private const val SESSION_ARTIST = "SESSION_ARTIST"
+14 −8
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import com.android.systemui.bluetooth.BroadcastDialogController
import com.android.systemui.media.controls.data.repository.MediaFilterRepository
import com.android.systemui.media.controls.domain.pipeline.MediaDataProcessor
import com.android.systemui.media.controls.domain.pipeline.getNotificationActions
import com.android.systemui.media.controls.shared.MediaLogger
import com.android.systemui.media.controls.shared.model.MediaControlModel
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.util.MediaSmartspaceLogger
@@ -59,6 +60,7 @@ constructor(
    private val lockscreenUserManager: NotificationLockscreenUserManager,
    private val mediaOutputDialogManager: MediaOutputDialogManager,
    private val broadcastDialogController: BroadcastDialogController,
    private val mediaLogger: MediaLogger,
) {

    val mediaControl: Flow<MediaControlModel?> =
@@ -73,7 +75,7 @@ constructor(
        instanceId: InstanceId,
        delayMs: Long,
        eventId: Int,
        location: Int
        location: Int,
    ): Boolean {
        logSmartspaceUserEvent(eventId, location)
        val dismissed =
@@ -81,7 +83,7 @@ constructor(
        if (!dismissed) {
            Log.w(
                TAG,
                "Manager failed to dismiss media of instanceId=$instanceId, Token uid=${token?.uid}"
                "Manager failed to dismiss media of instanceId=$instanceId, Token uid=${token?.uid}",
            )
        }
        return dismissed
@@ -120,13 +122,13 @@ constructor(
        expandable: Expandable,
        clickIntent: PendingIntent,
        eventId: Int,
        location: Int
        location: Int,
    ) {
        logSmartspaceUserEvent(eventId, location)
        if (!launchOverLockscreen(clickIntent)) {
            activityStarter.postStartActivityDismissingKeyguard(
                clickIntent,
                expandable.activityTransitionController(Cuj.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER)
                expandable.activityTransitionController(Cuj.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER),
            )
        }
    }
@@ -146,7 +148,7 @@ constructor(
            keyguardStateController.isShowing &&
                activityIntentHelper.wouldPendingShowOverLockscreen(
                    pendingIntent,
                    lockscreenUserManager.currentUserId
                    lockscreenUserManager.currentUserId,
                )
        if (showOverLockscreen) {
            try {
@@ -166,7 +168,7 @@ constructor(
    fun startMediaOutputDialog(
        expandable: Expandable,
        packageName: String,
        token: MediaSession.Token? = null
        token: MediaSession.Token? = null,
    ) {
        mediaOutputDialogManager.createAndShowWithController(
            packageName,
@@ -180,7 +182,7 @@ constructor(
        broadcastDialogController.createBroadcastDialogWithController(
            broadcastApp,
            packageName,
            expandable.dialogTransitionController()
            expandable.dialogTransitionController(),
        )
    }

@@ -188,10 +190,14 @@ constructor(
        repository.logSmartspaceCardUserEvent(
            eventId,
            MediaSmartspaceLogger.getSurface(location),
            instanceId = instanceId
            instanceId = instanceId,
        )
    }

    fun logMediaControlIsBound(artistName: CharSequence, songName: CharSequence) {
        mediaLogger.logMediaControlIsBound(instanceId, artistName, songName)
    }

    private fun Expandable.dialogController(): DialogTransitionAnimator.Controller? {
        return dialogTransitionController(
            cuj =
+26 −9
Original line number Diff line number Diff line
@@ -36,7 +36,7 @@ class MediaLogger @Inject constructor(@MediaLog private val buffer: LogBuffer) {
                bool1 = active
                str2 = reason
            },
            { "add media $str1, active: $bool1, reason: $str2" }
            { "add media $str1, active: $bool1, reason: $str2" },
        )
    }

@@ -48,7 +48,7 @@ class MediaLogger @Inject constructor(@MediaLog private val buffer: LogBuffer) {
                str1 = instanceId.toString()
                str2 = reason
            },
            { "removing media $str1, reason: $str2" }
            { "removing media $str1, reason: $str2" },
        )
    }

@@ -61,7 +61,7 @@ class MediaLogger @Inject constructor(@MediaLog private val buffer: LogBuffer) {
                bool1 = isActive
                str2 = reason
            },
            { "add recommendation $str1, active $bool1, reason: $str2" }
            { "add recommendation $str1, active $bool1, reason: $str2" },
        )
    }

@@ -74,7 +74,7 @@ class MediaLogger @Inject constructor(@MediaLog private val buffer: LogBuffer) {
                bool1 = immediately
                str2 = reason
            },
            { "removing recommendation $str1, immediate=$bool1, reason: $str2" }
            { "removing recommendation $str1, immediate=$bool1, reason: $str2" },
        )
    }

@@ -83,7 +83,7 @@ class MediaLogger @Inject constructor(@MediaLog private val buffer: LogBuffer) {
            TAG,
            LogLevel.DEBUG,
            { str1 = instanceId.toString() },
            { "adding media card $str1 to carousel" }
            { "adding media card $str1 to carousel" },
        )
    }

@@ -92,7 +92,7 @@ class MediaLogger @Inject constructor(@MediaLog private val buffer: LogBuffer) {
            TAG,
            LogLevel.DEBUG,
            { str1 = instanceId.toString() },
            { "removing media card $str1 from carousel" }
            { "removing media card $str1 from carousel" },
        )
    }

@@ -101,7 +101,7 @@ class MediaLogger @Inject constructor(@MediaLog private val buffer: LogBuffer) {
            TAG,
            LogLevel.DEBUG,
            { str1 = key },
            { "adding recommendation card $str1 to carousel" }
            { "adding recommendation card $str1 to carousel" },
        )
    }

@@ -110,7 +110,7 @@ class MediaLogger @Inject constructor(@MediaLog private val buffer: LogBuffer) {
            TAG,
            LogLevel.DEBUG,
            { str1 = key },
            { "removing recommendation card $str1 from carousel" }
            { "removing recommendation card $str1 from carousel" },
        )
    }

@@ -119,7 +119,24 @@ class MediaLogger @Inject constructor(@MediaLog private val buffer: LogBuffer) {
            TAG,
            LogLevel.DEBUG,
            { str1 = key },
            { "duplicate media notification $str1 posted" }
            { "duplicate media notification $str1 posted" },
        )
    }

    fun logMediaControlIsBound(
        instanceId: InstanceId,
        artistName: CharSequence,
        title: CharSequence,
    ) {
        buffer.log(
            TAG,
            LogLevel.DEBUG,
            {
                str1 = instanceId.toString()
                str2 = artistName.toString()
                str3 = title.toString()
            },
            { "binding media control, instance id= $str1, artist= $str2, title= $str3" },
        )
    }

+30 −27
Original line number Diff line number Diff line
@@ -80,8 +80,9 @@ object MediaControlViewBinder {
        mediaCard.repeatWhenAttached {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                launch {
                    viewModel.player.collectLatest { playerViewModel ->
                        playerViewModel?.let {
                    viewModel.player.collectLatest { player ->
                        player?.let {
                            if (viewModel.isNewPlayer(it)) {
                                bindMediaCard(
                                    viewHolder,
                                    viewController,
@@ -90,6 +91,8 @@ object MediaControlViewBinder {
                                    backgroundDispatcher,
                                    mainDispatcher,
                                )
                                viewModel.onMediaControlIsBound(it.artistName, it.titleName)
                            }
                        }
                    }
                }
@@ -143,7 +146,7 @@ object MediaControlViewBinder {
            viewHolder,
            viewModel.outputSwitcher,
            viewController,
            falsingManager
            falsingManager,
        )
        bindGutsViewModel(viewHolder, viewModel, viewController, falsingManager)
        bindActionButtons(viewHolder, viewModel, viewController, falsingManager)
@@ -157,7 +160,7 @@ object MediaControlViewBinder {
            viewController,
            backgroundDispatcher,
            mainDispatcher,
            isSongUpdated
            isSongUpdated,
        )

        if (viewModel.playTurbulenceNoise) {
@@ -259,12 +262,12 @@ object MediaControlViewBinder {
                if (buttonView.id == R.id.actionPrev) {
                    viewController.setUpPrevButtonInfo(
                        buttonModel.isEnabled,
                        buttonModel.notVisibleValue
                        buttonModel.notVisibleValue,
                    )
                } else if (buttonView.id == R.id.actionNext) {
                    viewController.setUpNextButtonInfo(
                        buttonModel.isEnabled,
                        buttonModel.notVisibleValue
                        buttonModel.notVisibleValue,
                    )
                }
                val animHandler = (buttonView.tag ?: AnimationBindHandler()) as AnimationBindHandler
@@ -295,7 +298,7 @@ object MediaControlViewBinder {
                        viewController.collapsedLayout,
                        visible,
                        buttonModel.notVisibleValue,
                        buttonModel.showInCollapsed
                        buttonModel.showInCollapsed,
                    )
                }
            }
@@ -350,7 +353,7 @@ object MediaControlViewBinder {
                        createTouchRippleAnimation(
                            button,
                            viewController.colorSchemeTransition,
                            multiRippleView
                            multiRippleView,
                        )
                    )

@@ -382,12 +385,12 @@ object MediaControlViewBinder {
                setVisibleAndAlpha(
                    expandedSet,
                    R.id.media_explicit_indicator,
                    viewModel.isExplicitVisible
                    viewModel.isExplicitVisible,
                )
                setVisibleAndAlpha(
                    collapsedSet,
                    R.id.media_explicit_indicator,
                    viewModel.isExplicitVisible
                    viewModel.isExplicitVisible,
                )

                // refreshState is required here to resize the text views (and prevent ellipsis)
@@ -398,7 +401,7 @@ object MediaControlViewBinder {
                // something is incorrectly bound, but needs to be run if other elements were
                // updated while the enter animation was running
                viewController.refreshState()
            }
            },
        )
    }

@@ -427,7 +430,7 @@ object MediaControlViewBinder {
                        viewModel.backgroundCover!!,
                        viewModel.colorScheme,
                        width,
                        height
                        height,
                    )
                } else {
                    ColorDrawable(Color.TRANSPARENT)
@@ -493,7 +496,7 @@ object MediaControlViewBinder {
        transitionDrawable: TransitionDrawable,
        layer: Int,
        targetWidth: Int,
        targetHeight: Int
        targetHeight: Int,
    ) {
        val drawable = transitionDrawable.getDrawable(layer) ?: return
        val width = drawable.intrinsicWidth
@@ -509,7 +512,7 @@ object MediaControlViewBinder {
        artworkIcon: android.graphics.drawable.Icon,
        mutableColorScheme: ColorScheme,
        width: Int,
        height: Int
        height: Int,
    ): LayerDrawable {
        val albumArt = MediaArtworkHelper.getScaledBackground(context, artworkIcon, width, height)
        return MediaArtworkHelper.setUpGradientColorOnDrawable(
@@ -517,7 +520,7 @@ object MediaControlViewBinder {
            context.getDrawable(R.drawable.qs_media_scrim)?.mutate() as GradientDrawable,
            mutableColorScheme,
            MEDIA_PLAYER_SCRIM_START_ALPHA,
            MEDIA_PLAYER_SCRIM_END_ALPHA
            MEDIA_PLAYER_SCRIM_END_ALPHA,
        )
    }

@@ -544,7 +547,7 @@ object MediaControlViewBinder {
    private fun createTouchRippleAnimation(
        button: ImageButton,
        colorSchemeTransition: ColorSchemeTransition,
        multiRippleView: MultiRippleView
        multiRippleView: MultiRippleView,
    ): RippleAnimation {
        val maxSize = (multiRippleView.width * 2).toFloat()
        return RippleAnimation(
@@ -562,7 +565,7 @@ object MediaControlViewBinder {
                baseRingFadeParams = null,
                sparkleRingFadeParams = null,
                centerFillFadeParams = null,
                shouldDistort = false
                shouldDistort = false,
            )
        )
    }
@@ -596,7 +599,7 @@ object MediaControlViewBinder {
        set: ConstraintSet,
        resId: Int,
        visible: Boolean,
        notVisibleValue: Int
        notVisibleValue: Int,
    ) {
        set.setVisibility(resId, if (visible) ConstraintSet.VISIBLE else notVisibleValue)
        set.setAlpha(resId, if (visible) 1.0f else 0.0f)
@@ -618,7 +621,7 @@ object MediaControlViewBinder {
        collapsedSet: ConstraintSet,
        visible: Boolean,
        notVisibleValue: Int,
        showInCollapsed: Boolean
        showInCollapsed: Boolean,
    ) {
        if (notVisibleValue == ConstraintSet.INVISIBLE) {
            // Since time views should appear instead of buttons.
+13 −1
Original line number Diff line number Diff line
@@ -32,4 +32,16 @@ data class MediaActionViewModel(
    val buttonId: Int? = null,
    val isEnabled: Boolean,
    val onClicked: (Int) -> Unit,
)
) {
    fun contentEquals(other: MediaActionViewModel?): Boolean {
        return other?.let {
            contentDescription == other.contentDescription &&
                isVisibleWhenScrubbing == other.isVisibleWhenScrubbing &&
                notVisibleValue == other.notVisibleValue &&
                showInCollapsed == other.showInCollapsed &&
                rebindId == other.rebindId &&
                buttonId == other.buttonId &&
                isEnabled == other.isEnabled
        } ?: false
    }
}
Loading