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

Commit fef3243e authored by Ahmed Mehfooz's avatar Ahmed Mehfooz
Browse files

[ROSP] Make Media Chip functional without flexiglass

This CL adds a new mediaControlChipModelLegacy flow in
MediaControlChipInteractor which directly listens to the
MediaDataManager to provide a MediaControlChipModel. This allows the
chip to function without flexiglass being enabled.

Also, updates the MediaControlChipInteractor to handle both flexiglass
on/off scenarios and creates a new isEnabled field which is used to
ensure this feature is only enabled on SystemUITitan. This is done by
setting the isEnabled field in a MediaControlChipStartable.

Bug: b/394887130
Flag: com.android.systemui.status_bar_popup_chips

Test: parameterized
tests for MediaControlChipInteractor
Test: Make sure media control chip
is functional with flexiglass on/off.

Change-Id: I21db9377eed72b1f485df3d0d49adb71149536b1
parent c64742c0
Loading
Loading
Loading
Loading
+77 −40
Original line number Diff line number Diff line
@@ -17,100 +17,119 @@
package com.android.systemui.statusbar.featurepods.media.domain.interactor

import android.graphics.drawable.Drawable
import androidx.test.ext.junit.runners.AndroidJUnit4
import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.parameterizeSceneContainerFlag
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
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.MediaData
import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.mock
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters

@RunWith(ParameterizedAndroidJunit4::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class MediaControlChipInteractorTest : SysuiTestCase() {

class MediaControlChipInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
    private val underTest = kosmos.mediaControlChipInteractor
    private val mediaFilterRepository = kosmos.mediaFilterRepository
    private val Kosmos.underTest by Kosmos.Fixture { kosmos.mediaControlChipInteractor }
    @Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener>

    companion object {
        @JvmStatic
        @Parameters(name = "{0}")
        fun getParams(): List<FlagsParameterization> {
            return parameterizeSceneContainerFlag()
        }
    }

    @Before
    fun setUp() {
        kosmos.underTest.initialize()
        MockitoAnnotations.initMocks(this)
    }

    init {
        mSetFlagsRule.setFlagsParameterization(flags)
    }

    @Test
    fun mediaControlModel_noActiveMedia_null() =
    fun mediaControlChipModel_noActiveMedia_null() =
        kosmos.runTest {
            val model by collectLastValue(underTest.mediaControlModel)
            val model by collectLastValue(underTest.mediaControlChipModel)

            assertThat(model).isNull()
        }

    @Test
    fun mediaControlModel_activeMedia_notNull() =
    fun mediaControlChipModel_activeMedia_notNull() =
        kosmos.runTest {
            val model by collectLastValue(underTest.mediaControlModel)
            val model by collectLastValue(underTest.mediaControlChipModel)

            val userMedia = MediaData(active = true)
            val instanceId = userMedia.instanceId

            mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
            mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
            updateMedia(userMedia)

            assertThat(model).isNotNull()
        }

    @Test
    fun mediaControlModel_mediaRemoved_null() =
    fun mediaControlChipModel_mediaRemoved_null() =
        kosmos.runTest {
            val model by collectLastValue(underTest.mediaControlModel)
            val model by collectLastValue(underTest.mediaControlChipModel)

            val userMedia = MediaData(active = true)
            val instanceId = userMedia.instanceId

            mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
            mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
            updateMedia(userMedia)

            assertThat(model).isNotNull()

            assertThat(mediaFilterRepository.removeSelectedUserMediaEntry(instanceId, userMedia))
                .isTrue()
            mediaFilterRepository.addMediaDataLoadingState(
                MediaDataLoadingModel.Removed(instanceId)
            )
            removeMedia(userMedia)

            assertThat(model).isNull()
        }

    @Test
    fun mediaControlModel_songNameChanged_emitsUpdatedModel() =
    fun mediaControlChipModel_songNameChanged_emitsUpdatedModel() =
        kosmos.runTest {
            val model by collectLastValue(underTest.mediaControlModel)
            val model by collectLastValue(underTest.mediaControlChipModel)

            val initialSongName = "Initial Song"
            val newSongName = "New Song"
            val userMedia = MediaData(active = true, song = initialSongName)
            val instanceId = userMedia.instanceId

            mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
            mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
            updateMedia(userMedia)

            assertThat(model).isNotNull()
            assertThat(model?.songName).isEqualTo(initialSongName)

            val updatedUserMedia = userMedia.copy(song = newSongName)
            mediaFilterRepository.addSelectedUserMediaEntry(updatedUserMedia)
            updateMedia(updatedUserMedia)

            assertThat(model?.songName).isEqualTo(newSongName)
        }

    @Test
    fun mediaControlModel_playPauseActionChanges_emitsUpdatedModel() =
    fun mediaControlChipModel_playPauseActionChanges_emitsUpdatedModel() =
        kosmos.runTest {
            val model by collectLastValue(underTest.mediaControlModel)
            val model by collectLastValue(underTest.mediaControlChipModel)

            val mockDrawable = mock<Drawable>()

@@ -123,9 +142,7 @@ class MediaControlChipInteractorTest : SysuiTestCase() {
                )
            val mediaButton = MediaButton(playOrPause = initialAction)
            val userMedia = MediaData(active = true, semanticActions = mediaButton)
            val instanceId = userMedia.instanceId
            mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
            mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
            updateMedia(userMedia)

            assertThat(model).isNotNull()
            assertThat(model?.playOrPause).isEqualTo(initialAction)
@@ -139,15 +156,15 @@ class MediaControlChipInteractorTest : SysuiTestCase() {
                )
            val updatedMediaButton = MediaButton(playOrPause = newAction)
            val updatedUserMedia = userMedia.copy(semanticActions = updatedMediaButton)
            mediaFilterRepository.addSelectedUserMediaEntry(updatedUserMedia)
            updateMedia(updatedUserMedia)

            assertThat(model?.playOrPause).isEqualTo(newAction)
        }

    @Test
    fun mediaControlModel_playPauseActionRemoved_playPauseNull() =
    fun mediaControlChipModel_playPauseActionRemoved_playPauseNull() =
        kosmos.runTest {
            val model by collectLastValue(underTest.mediaControlModel)
            val model by collectLastValue(underTest.mediaControlChipModel)

            val mockDrawable = mock<Drawable>()

@@ -160,16 +177,36 @@ class MediaControlChipInteractorTest : SysuiTestCase() {
                )
            val mediaButton = MediaButton(playOrPause = initialAction)
            val userMedia = MediaData(active = true, semanticActions = mediaButton)
            val instanceId = userMedia.instanceId
            mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
            mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
            updateMedia(userMedia)

            assertThat(model).isNotNull()
            assertThat(model?.playOrPause).isEqualTo(initialAction)

            val updatedUserMedia = userMedia.copy(semanticActions = MediaButton())
            mediaFilterRepository.addSelectedUserMediaEntry(updatedUserMedia)
            updateMedia(updatedUserMedia)

            assertThat(model?.playOrPause).isNull()
        }

    private fun updateMedia(mediaData: MediaData) {
        if (SceneContainerFlag.isEnabled) {
            val instanceId = mediaData.instanceId
            mediaFilterRepository.addSelectedUserMediaEntry(mediaData)
            mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
        } else {
            kosmos.underTest.updateMediaControlChipModelLegacy(mediaData)
        }
    }

    private fun removeMedia(mediaData: MediaData) {
        if (SceneContainerFlag.isEnabled) {
            val instanceId = mediaData.instanceId
            mediaFilterRepository.removeSelectedUserMediaEntry(instanceId, mediaData)
            mediaFilterRepository.addMediaDataLoadingState(
                MediaDataLoadingModel.Removed(instanceId)
            )
        } else {
            kosmos.underTest.updateMediaControlChipModelLegacy(null)
        }
    }
}
+51 −14
Original line number Diff line number Diff line
@@ -16,26 +16,57 @@

package com.android.systemui.statusbar.featurepods.media.ui.viewmodel

import androidx.test.ext.junit.runners.AndroidJUnit4
import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.parameterizeSceneContainerFlag
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.featurepods.media.domain.interactor.mediaControlChipInteractor
import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import org.junit.Before
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.MockitoAnnotations
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters

@SmallTest
@RunWith(AndroidJUnit4::class)
class MediaControlChipViewModelTest : SysuiTestCase() {
@RunWith(ParameterizedAndroidJunit4::class)
class MediaControlChipViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
    private val underTest = kosmos.mediaControlChipViewModel
    private val mediaControlChipInteractor by lazy { kosmos.mediaControlChipInteractor }
    private val Kosmos.underTest by Kosmos.Fixture { kosmos.mediaControlChipViewModel }
    @Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener>

    companion object {
        @JvmStatic
        @Parameters(name = "{0}")
        fun getParams(): List<FlagsParameterization> {
            return parameterizeSceneContainerFlag()
        }
    }

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)
        mediaControlChipInteractor.initialize()
    }

    init {
        mSetFlagsRule.setFlagsParameterization(flags)
    }

    @Test
    fun chip_noActiveMedia_IsHidden() =
@@ -51,10 +82,7 @@ class MediaControlChipViewModelTest : SysuiTestCase() {
            val chip by collectLastValue(underTest.chip)

            val userMedia = MediaData(active = true, song = "test")
            val instanceId = userMedia.instanceId

            mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
            mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
            updateMedia(userMedia)

            assertThat(chip).isInstanceOf(PopupChipModel.Shown::class.java)
        }
@@ -67,16 +95,25 @@ class MediaControlChipViewModelTest : SysuiTestCase() {
            val initialSongName = "Initial Song"
            val newSongName = "New Song"
            val userMedia = MediaData(active = true, song = initialSongName)
            val instanceId = userMedia.instanceId

            mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
            mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))

            updateMedia(userMedia)
            assertThat(chip).isInstanceOf(PopupChipModel.Shown::class.java)
            assertThat((chip as PopupChipModel.Shown).chipText).isEqualTo(initialSongName)

            val updatedUserMedia = userMedia.copy(song = newSongName)
            mediaFilterRepository.addSelectedUserMediaEntry(updatedUserMedia)
            updateMedia(updatedUserMedia)

            assertThat((chip as PopupChipModel.Shown).chipText).isEqualTo(newSongName)
        }

    private fun updateMedia(mediaData: MediaData) {
        if (SceneContainerFlag.isEnabled) {
            val instanceId = mediaData.instanceId
            kosmos.mediaFilterRepository.addSelectedUserMediaEntry(mediaData)
            kosmos.mediaFilterRepository.addMediaDataLoadingState(
                MediaDataLoadingModel.Loaded(instanceId)
            )
        } else {
            mediaControlChipInteractor.updateMediaControlChipModelLegacy(mediaData)
        }
    }
}
+6 −0
Original line number Diff line number Diff line
@@ -143,6 +143,12 @@ interface MediaDataManager {
         *   place immediately.
         */
        override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {}

        /**
         * Called whenever the current active media notification changes. Should only be used if
         * [SceneContainerFlag] is disabled
         */
        override fun onCurrentActiveMediaChanged(key: String?, data: MediaData?) {}
    }

    companion object {
+3 −0
Original line number Diff line number Diff line
@@ -1434,6 +1434,9 @@ class MediaDataProcessor(
         *   place immediately.
         */
        fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean = true) {}

        /** Called whenever the current active media notification changes */
        fun onCurrentActiveMediaChanged(key: String?, data: MediaData?) {}
    }

    /**
+27 −0
Original line number Diff line number Diff line
@@ -87,6 +87,7 @@ import com.android.systemui.shared.system.SysUiStatsLog.SMART_SPACE_CARD_REPORTE
import com.android.systemui.shared.system.SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY as SSPACE_CARD_REPORTED__DREAM_OVERLAY
import com.android.systemui.shared.system.SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN as SSPACE_CARD_REPORTED__LOCKSCREEN
import com.android.systemui.shared.system.SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE
import com.android.systemui.statusbar.featurepods.media.domain.interactor.MediaControlChipInteractor
import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -155,6 +156,7 @@ constructor(
    private val mediaCarouselViewModel: MediaCarouselViewModel,
    private val mediaViewControllerFactory: Provider<MediaViewController>,
    private val deviceEntryInteractor: DeviceEntryInteractor,
    private val mediaControlChipInteractor: MediaControlChipInteractor,
) : Dumpable {
    /** The current width of the carousel */
    var currentCarouselWidth: Int = 0
@@ -957,6 +959,9 @@ constructor(
                }
        }
        mediaCarouselScrollHandler.onPlayersChanged()
        mediaControlChipInteractor.updateMediaControlChipModelLegacy(
            MediaPlayerData.getFirstActiveMediaData()
        )
        MediaPlayerData.updateVisibleMediaPlayers()
        // Automatically scroll to the active player if needed
        if (shouldScrollToKey) {
@@ -1015,6 +1020,9 @@ constructor(
                            )
                            updatePageIndicator()
                            mediaCarouselScrollHandler.onPlayersChanged()
                            mediaControlChipInteractor.updateMediaControlChipModelLegacy(
                                MediaPlayerData.getFirstActiveMediaData()
                            )
                            mediaFrame.requiresRemeasuring = true
                            onUiExecutionEnd?.run()
                        }
@@ -1023,6 +1031,9 @@ constructor(
                    updatePlayer(key, data, isSsReactivated, curVisibleMediaKey, existingPlayer)
                    updatePageIndicator()
                    mediaCarouselScrollHandler.onPlayersChanged()
                    mediaControlChipInteractor.updateMediaControlChipModelLegacy(
                        MediaPlayerData.getFirstActiveMediaData()
                    )
                    mediaFrame.requiresRemeasuring = true
                    onUiExecutionEnd?.run()
                }
@@ -1036,6 +1047,9 @@ constructor(
                }
                updatePageIndicator()
                mediaCarouselScrollHandler.onPlayersChanged()
                mediaControlChipInteractor.updateMediaControlChipModelLegacy(
                    MediaPlayerData.getFirstActiveMediaData()
                )
                mediaFrame.requiresRemeasuring = true
                onUiExecutionEnd?.run()
            }
@@ -1194,6 +1208,9 @@ constructor(
            mediaContent.removeView(removed.recommendationViewHolder?.recommendations)
            removed.onDestroy()
            mediaCarouselScrollHandler.onPlayersChanged()
            mediaControlChipInteractor.updateMediaControlChipModelLegacy(
                MediaPlayerData.getFirstActiveMediaData()
            )
            updatePageIndicator()

            if (dismissMediaData) {
@@ -1928,6 +1945,16 @@ internal object MediaPlayerData {

    fun visiblePlayerKeys() = visibleMediaPlayers.values

    /** Returns the [MediaData] associated with the first mediaPlayer in the mediaCarousel. */
    fun getFirstActiveMediaData(): MediaData? {
        mediaPlayers.entries.forEach { entry ->
            if (!entry.key.isSsMediaRec && entry.key.data.active) {
                return entry.key.data
            }
        }
        return null
    }

    /** Returns the index of the first non-timeout media. */
    fun firstActiveMediaIndex(): Int {
        mediaPlayers.entries.forEachIndexed { index, e ->
Loading