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

Commit d9a62712 authored by Beth Thibodeau's avatar Beth Thibodeau
Browse files

Add media composable to quick settings

Flag: com.android.systemui.media_controls_in_compose
Bug: 397989775
Test: manual - play media, verify behavior in QS/QQS in portrait and landscape
Change-Id: I6771fc6c63d3c99e9e32f444331e0d2a4b772116
parent d68d2f86
Loading
Loading
Loading
Loading
+6 −1
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import com.android.systemui.media.controls.ui.controller.MediaLocation
import com.android.systemui.media.controls.ui.controller.mediaHostStatesManager
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.qs.composefragment.dagger.usingMediaInComposeFragment
import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
import com.android.systemui.shade.domain.interactor.enableDualShade
import com.android.systemui.shade.domain.interactor.enableSingleShade
import com.android.systemui.shade.domain.interactor.enableSplitShade
@@ -54,7 +55,10 @@ class MediaInRowInLandscapeViewModelTest(private val testData: TestData) : Sysui
    private val kosmos = testKosmos().apply { usingMediaInComposeFragment = testData.usingMedia }

    private val underTest by lazy {
        kosmos.mediaInRowInLandscapeViewModelFactory.create(TESTED_MEDIA_LOCATION)
        kosmos.mediaInRowInLandscapeViewModelFactory.create(
            TESTED_MEDIA_LOCATION,
            TESTED_MEDIA_BEHAVIOR,
        )
    }

    @Before
@@ -110,6 +114,7 @@ class MediaInRowInLandscapeViewModelTest(private val testData: TestData) : Sysui

    companion object {
        private const val TESTED_MEDIA_LOCATION = LOCATION_QS
        private val TESTED_MEDIA_BEHAVIOR = QuickSettingsContainerViewModel.mediaUiBehavior

        @JvmStatic
        @Parameters(name = "{0}")
+43 −10
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.qs.composefragment.dagger.usingMediaInComposeFragment
import com.android.systemui.qs.panels.data.repository.QSColumnsRepository
import com.android.systemui.qs.panels.data.repository.qsColumnsRepository
import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.disableDualShade
import com.android.systemui.shade.domain.interactor.enableDualShade
@@ -76,7 +77,7 @@ class QSColumnsViewModelTest : SysuiTestCase() {
    @Test
    fun mediaLocationNull_singleOrSplit_alwaysSingleShadeColumns() =
        kosmos.runTest {
            val underTest = qsColumnsViewModelFactory.create(null)
            val underTest = qsColumnsViewModelFactory.create(null, null)
            underTest.activateIn(testScope)
            disableDualShade()

@@ -96,7 +97,7 @@ class QSColumnsViewModelTest : SysuiTestCase() {
    @EnableSceneContainer
    fun mediaLocationNull_dualShade_alwaysDualShadeColumns() =
        kosmos.runTest {
            val underTest = qsColumnsViewModelFactory.create(null)
            val underTest = qsColumnsViewModelFactory.create(null, null)
            underTest.activateIn(testScope)
            enableDualShade()

@@ -116,7 +117,11 @@ class QSColumnsViewModelTest : SysuiTestCase() {
    @EnableSceneContainer
    fun mediaLocationQS_dualShade_alwaysDualShadeColumns() =
        kosmos.runTest {
            val underTest = qsColumnsViewModelFactory.create(LOCATION_QS)
            val underTest =
                qsColumnsViewModelFactory.create(
                    LOCATION_QS,
                    QuickSettingsContainerViewModel.mediaUiBehavior,
                )
            underTest.activateIn(testScope)
            enableDualShade()

@@ -135,7 +140,11 @@ class QSColumnsViewModelTest : SysuiTestCase() {
    @EnableSceneContainer
    fun mediaLocationQQS_dualShade_alwaysDualShadeColumns() =
        kosmos.runTest {
            val underTest = qsColumnsViewModelFactory.create(LOCATION_QQS)
            val underTest =
                qsColumnsViewModelFactory.create(
                    LOCATION_QQS,
                    QuickQuickSettingsViewModel.mediaUiBehavior,
                )
            underTest.activateIn(testScope)
            enableDualShade()

@@ -153,7 +162,11 @@ class QSColumnsViewModelTest : SysuiTestCase() {
    @Test
    fun mediaLocationQS_singleOrSplit_halfColumnsOnCorrectConfigurationAndVisible() =
        kosmos.runTest {
            val underTest = qsColumnsViewModelFactory.create(LOCATION_QS)
            val underTest =
                qsColumnsViewModelFactory.create(
                    LOCATION_QS,
                    QuickSettingsContainerViewModel.mediaUiBehavior,
                )
            underTest.activateIn(testScope)
            disableDualShade()

@@ -173,7 +186,11 @@ class QSColumnsViewModelTest : SysuiTestCase() {
    @Test
    fun mediaLocationQQS_singleOrSplit_halfColumnsOnCorrectConfigurationAndVisible() =
        kosmos.runTest {
            val underTest = qsColumnsViewModelFactory.create(LOCATION_QQS)
            val underTest =
                qsColumnsViewModelFactory.create(
                    LOCATION_QQS,
                    QuickQuickSettingsViewModel.mediaUiBehavior,
                )
            underTest.activateIn(testScope)
            disableDualShade()

@@ -193,7 +210,11 @@ class QSColumnsViewModelTest : SysuiTestCase() {
    @Test
    fun largeSpan_normalTiles_ignoreColumns() =
        kosmos.runTest {
            val underTest = qsColumnsViewModelFactory.create(LOCATION_QQS)
            val underTest =
                qsColumnsViewModelFactory.create(
                    LOCATION_QQS,
                    QuickQuickSettingsViewModel.mediaUiBehavior,
                )
            underTest.activateIn(testScope)

            // Set extra large tiles to false
@@ -212,7 +233,11 @@ class QSColumnsViewModelTest : SysuiTestCase() {
    @Test
    fun largeSpan_extraLargeTiles_tracksColumns() =
        kosmos.runTest {
            val underTest = qsColumnsViewModelFactory.create(LOCATION_QQS)
            val underTest =
                qsColumnsViewModelFactory.create(
                    LOCATION_QQS,
                    QuickQuickSettingsViewModel.mediaUiBehavior,
                )
            underTest.activateIn(testScope)

            // Set extra large tiles to true
@@ -237,7 +262,11 @@ class QSColumnsViewModelTest : SysuiTestCase() {
    @Test
    fun largeSpan_extraLargeTiles_tracksMaxWidth() =
        kosmos.runTest {
            val underTest = qsColumnsViewModelFactory.create(LOCATION_QQS)
            val underTest =
                qsColumnsViewModelFactory.create(
                    LOCATION_QQS,
                    QuickQuickSettingsViewModel.mediaUiBehavior,
                )
            underTest.activateIn(testScope)

            // Set extra large tiles to true
@@ -262,7 +291,11 @@ class QSColumnsViewModelTest : SysuiTestCase() {
    @Test
    fun largeSpan_tracksExtraLargeTiles() =
        kosmos.runTest {
            val underTest = qsColumnsViewModelFactory.create(LOCATION_QQS)
            val underTest =
                qsColumnsViewModelFactory.create(
                    LOCATION_QQS,
                    QuickQuickSettingsViewModel.mediaUiBehavior,
                )
            underTest.activateIn(testScope)

            // Set extra large tiles to false
+56 −28
Original line number Diff line number Diff line
@@ -125,8 +125,13 @@ import com.android.systemui.keyboard.shortcut.ui.composable.ProvideShortcutHelpe
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.lifecycle.setSnapshotBinding
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.media.controls.ui.controller.MediaViewLogger
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.remedia.ui.compose.Media
import com.android.systemui.media.remedia.ui.compose.MediaPresentationStyle
import com.android.systemui.media.remedia.ui.compose.MediaUiBehavior
import com.android.systemui.media.remedia.ui.viewmodel.MediaViewModel
import com.android.systemui.plugins.qs.QS
import com.android.systemui.plugins.qs.QSContainerController
import com.android.systemui.qs.composefragment.SceneKeys.QuickQuickSettings
@@ -748,6 +753,9 @@ constructor(
                                modifier = Modifier.requiredHeightIn(max = Dp.Infinity),
                                mediaHost = viewModel.qqsMediaHost,
                                mediaLogger = mediaLogger,
                                mediaCarouselInteractor = viewModel.mediaCarouselInteractor,
                                mediaViewModelFactory = viewModel.mediaViewModelFactory,
                                behavior = viewModel.qqsMediaUiBehavior,
                            )
                        }
                    }
@@ -904,6 +912,9 @@ constructor(
                                    MediaObject(
                                        mediaHost = viewModel.qsMediaHost,
                                        mediaLogger = mediaLogger,
                                        mediaViewModelFactory = viewModel.mediaViewModelFactory,
                                        mediaCarouselInteractor = viewModel.mediaCarouselInteractor,
                                        behavior = viewModel.qsMediaUiBehavior,
                                        update = { translationY = viewModel.qsMediaTranslationY },
                                    )
                                }
@@ -1384,8 +1395,20 @@ private fun MediaObject(
    mediaHost: MediaHost,
    modifier: Modifier = Modifier,
    mediaLogger: MediaViewLogger,
    mediaViewModelFactory: MediaViewModel.Factory,
    mediaCarouselInteractor: MediaCarouselInteractor,
    behavior: MediaUiBehavior,
    update: UniqueObjectHostView.() -> Unit = {},
) {
    if (Flags.mediaControlsInCompose()) {
        Media(
            viewModelFactory = mediaViewModelFactory,
            presentationStyle = MediaPresentationStyle.Default,
            behavior = behavior,
            onDismissed = { mediaCarouselInteractor.onSwipeToDismiss() },
            modifier = modifier,
        )
    } else {
        Box {
            AndroidView(
                modifier = modifier,
@@ -1405,13 +1428,17 @@ private fun MediaObject(
                    val width = mediaHost.hostView.width
                    var measure = false
                    mediaHost.hostView.children.forEach { child ->
                    if (child is FrameLayout && (height > child.height || width > child.width)) {
                        if (
                            child is FrameLayout && (height > child.height || width > child.width)
                        ) {
                            measure = true
                            child.layoutParams = FrameLayout.LayoutParams(width, height)
                        }
                    }
                    if (measure) {
                    mediaHost.hostView.measurementManager.onMeasure(MeasurementInput(width, height))
                        mediaHost.hostView.measurementManager.onMeasure(
                            MeasurementInput(width, height)
                        )
                        mediaLogger.logMediaSize("update size in compose", width, height)
                    }
                },
@@ -1419,6 +1446,7 @@ private fun MediaObject(
            )
        }
    }
}

@Composable
@VisibleForTesting
+29 −6
Original line number Diff line number Diff line
@@ -51,6 +51,9 @@ import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.controls.ui.view.MediaHostState
import com.android.systemui.media.dagger.MediaModule.QS_PANEL
import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
import com.android.systemui.media.remedia.ui.compose.MediaUiBehavior
import com.android.systemui.media.remedia.ui.viewmodel.MediaCarouselVisibility
import com.android.systemui.media.remedia.ui.viewmodel.MediaViewModel
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.QSEvent
@@ -116,14 +119,17 @@ constructor(
    @Named(QSFragmentComposeModule.QS_USING_MEDIA_PLAYER) private val usingMedia: Boolean,
    private val uiEventLogger: UiEventLogger,
    @Assisted private val lifecycleScope: LifecycleCoroutineScope,
    private val mediaCarouselInteractor: MediaCarouselInteractor,
    val mediaCarouselInteractor: MediaCarouselInteractor,
    val mediaViewModelFactory: MediaViewModel.Factory,
) : Dumpable, ExclusiveActivatable() {

    val containerViewModel = containerViewModelFactory.create(supportsBrightnessMirroring = true)
    val quickQuickSettingsViewModel = quickQuickSettingsViewModelFactory.create()

    private val qqsMediaInRowViewModel = mediaInRowInLandscapeViewModelFactory.create(LOCATION_QQS)
    private val qsMediaInRowViewModel = mediaInRowInLandscapeViewModelFactory.create(LOCATION_QS)
    private val qqsMediaInRowViewModel =
        mediaInRowInLandscapeViewModelFactory.create(LOCATION_QQS, qqsMediaUiBehavior)
    private val qsMediaInRowViewModel =
        mediaInRowInLandscapeViewModelFactory.create(LOCATION_QS, qsMediaUiBehavior)

    private val hydrator = Hydrator("QSFragmentComposeViewModel.hydrator", tableLogBuffer)

@@ -274,12 +280,26 @@ constructor(
            initialValue = usingMedia,
            source =
                if (usingMedia) {
                    mediaHostVisible(qqsMediaHost, mediaCarouselInteractor)
                    mediaHostVisible(qqsMediaHost, qqsMediaUiBehavior, mediaCarouselInteractor)
                } else {
                    flowOf(false)
                },
        )

    val qsMediaUiBehavior: MediaUiBehavior
        get() =
            MediaUiBehavior(
                isCarouselDismissible = false,
                carouselVisibility = MediaCarouselVisibility.WhenNotEmpty,
            )

    val qqsMediaUiBehavior: MediaUiBehavior
        get() =
            MediaUiBehavior(
                isCarouselDismissible = true,
                carouselVisibility = MediaCarouselVisibility.WhenAnyCardIsActive,
            )

    val qqsMediaInRow: Boolean
        get() = qqsMediaInRowViewModel.shouldMediaShowInRow

@@ -289,7 +309,7 @@ constructor(
            initialValue = usingMedia,
            source =
                if (usingMedia) {
                    mediaHostVisible(qsMediaHost, mediaCarouselInteractor)
                    mediaHostVisible(qsMediaHost, qsMediaUiBehavior, mediaCarouselInteractor)
                } else {
                    flowOf(false)
                },
@@ -622,10 +642,13 @@ private val SHORT_PARALLAX_AMOUNT = 0.1f
 */
private fun mediaHostVisible(
    mediaHost: MediaHost,
    mediaUiBehavior: MediaUiBehavior,
    mediaCarouselInteractor: MediaCarouselInteractor,
): Flow<Boolean> {
    if (Flags.mediaControlsInCompose()) {
        return if (mediaHost.showsOnlyActiveMedia) {
        return if (
            mediaUiBehavior.carouselVisibility == MediaCarouselVisibility.WhenAnyCardIsActive
        ) {
            mediaCarouselInteractor.hasActiveMedia
        } else {
            mediaCarouselInteractor.hasAnyMedia
+6 −2
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package com.android.systemui.qs.panels.ui.viewmodel

import androidx.compose.runtime.getValue
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QS
@@ -24,6 +23,7 @@ import com.android.systemui.qs.panels.shared.model.SizedTileImpl
import com.android.systemui.qs.panels.ui.dialog.QSResetDialogDelegate
import com.android.systemui.qs.panels.ui.viewmodel.PaginatableViewModel.Companion.splitInRows
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.awaitCancellation
@@ -42,7 +42,11 @@ constructor(
    private val hydrator = Hydrator("InfiniteGridViewModel.hydrator")

    val iconTilesViewModel = dynamicIconTilesViewModelFactory.create()
    val columnsWithMediaViewModel = columnsWithMediaViewModelFactory.create(LOCATION_QS)
    val columnsWithMediaViewModel =
        columnsWithMediaViewModelFactory.create(
            LOCATION_QS,
            QuickSettingsContainerViewModel.mediaUiBehavior,
        )

    override val pageKeys: Array<Any>
        get() =
Loading