diff options
24 files changed, 1086 insertions, 73 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt index 9fe9ed2bf47a..5cba325450e6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt @@ -36,6 +36,7 @@ import com.android.systemui.qs.composefragment.viewmodel.MediaState.ANY_MEDIA import com.android.systemui.qs.composefragment.viewmodel.MediaState.NO_MEDIA import com.android.systemui.qs.fgsManagerController import com.android.systemui.qs.panels.domain.interactor.tileSquishinessInteractor +import com.android.systemui.qs.panels.ui.viewmodel.setConfigurationForMediaInRow import com.android.systemui.res.R import com.android.systemui.shade.largeScreenHeaderHelper import com.android.systemui.statusbar.StatusBarState @@ -269,6 +270,54 @@ class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest() } } + @Test + fun mediaNotInRow() = + with(kosmos) { + testScope.testWithinLifecycle { + setConfigurationForMediaInRow(mediaInRow = false) + setMediaState(ACTIVE_MEDIA) + + assertThat(underTest.qqsMediaInRow).isFalse() + assertThat(underTest.qsMediaInRow).isFalse() + } + } + + @Test + fun mediaInRow_mediaActive_bothInRow() = + with(kosmos) { + testScope.testWithinLifecycle { + setConfigurationForMediaInRow(mediaInRow = true) + setMediaState(ACTIVE_MEDIA) + + assertThat(underTest.qqsMediaInRow).isTrue() + assertThat(underTest.qsMediaInRow).isTrue() + } + } + + @Test + fun mediaInRow_mediaRecommendation_onlyQSInRow() = + with(kosmos) { + testScope.testWithinLifecycle { + setConfigurationForMediaInRow(mediaInRow = true) + setMediaState(ANY_MEDIA) + + assertThat(underTest.qqsMediaInRow).isFalse() + assertThat(underTest.qsMediaInRow).isTrue() + } + } + + @Test + fun mediaInRow_correctConfig_noMediaVisible_noMediaInRow() = + with(kosmos) { + testScope.testWithinLifecycle { + setConfigurationForMediaInRow(mediaInRow = true) + setMediaState(NO_MEDIA) + + assertThat(underTest.qqsMediaInRow).isFalse() + assertThat(underTest.qsMediaInRow).isFalse() + } + } + private fun TestScope.setMediaState(state: MediaState) { with(kosmos) { val activeMedia = state == ACTIVE_MEDIA diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/QSFragmentComposeTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/QSFragmentComposeTest.kt new file mode 100644 index 000000000000..ab78029684d4 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/QSFragmentComposeTest.kt @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.test.getBoundsInRoot +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.DpRect +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.width +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.qs.composefragment.QuickQuickSettingsLayout +import com.android.systemui.qs.composefragment.QuickSettingsLayout +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SmallTest +class QSFragmentComposeTest : SysuiTestCase() { + + @get:Rule val composeTestRule = createComposeRule() + + @Test + fun portraitLayout_qqs() { + composeTestRule.setContent { + QuickQuickSettingsLayout( + tiles = { Tiles(TILES_HEIGHT_PORTRAIT) }, + media = { Media() }, + mediaInRow = false, + ) + } + + composeTestRule.waitForIdle() + + val tilesBounds = composeTestRule.onNodeWithTag(TILES).getBoundsInRoot() + val mediaBounds = composeTestRule.onNodeWithTag(MEDIA).getBoundsInRoot() + + // All nodes aligned in a column + assertThat(tilesBounds.left).isEqualTo(mediaBounds.left) + assertThat(tilesBounds.right).isEqualTo(mediaBounds.right) + assertThat(tilesBounds.bottom).isLessThan(mediaBounds.top) + } + + @Test + fun landscapeLayout_qqs() { + composeTestRule.setContent { + QuickQuickSettingsLayout( + tiles = { Tiles(TILES_HEIGHT_LANDSCAPE) }, + media = { Media() }, + mediaInRow = true, + ) + } + + composeTestRule.waitForIdle() + + val tilesBounds = composeTestRule.onNodeWithTag(TILES).getBoundsInRoot() + val mediaBounds = composeTestRule.onNodeWithTag(MEDIA).getBoundsInRoot() + + // Media to the right of tiles + assertThat(tilesBounds.right).isLessThan(mediaBounds.left) + // "Same" width + assertThat((tilesBounds.width - mediaBounds.width).abs()).isAtMost(1.dp) + // Vertically centered + assertThat((tilesBounds.centerY - mediaBounds.centerY).abs()).isAtMost(1.dp) + } + + @Test + fun portraitLayout_qs() { + composeTestRule.setContent { + QuickSettingsLayout( + brightness = { Brightness() }, + tiles = { Tiles(TILES_HEIGHT_PORTRAIT) }, + media = { Media() }, + mediaInRow = false, + ) + } + + composeTestRule.waitForIdle() + + val brightnessBounds = composeTestRule.onNodeWithTag(BRIGHTNESS).getBoundsInRoot() + val tilesBounds = composeTestRule.onNodeWithTag(TILES).getBoundsInRoot() + val mediaBounds = composeTestRule.onNodeWithTag(MEDIA).getBoundsInRoot() + + assertThat(brightnessBounds.left).isEqualTo(tilesBounds.left) + assertThat(tilesBounds.left).isEqualTo(mediaBounds.left) + + assertThat(brightnessBounds.right).isEqualTo(tilesBounds.right) + assertThat(tilesBounds.right).isEqualTo(mediaBounds.right) + + assertThat(brightnessBounds.bottom).isLessThan(tilesBounds.top) + assertThat(tilesBounds.bottom).isLessThan(mediaBounds.top) + } + + @Test + fun landscapeLayout_qs() { + composeTestRule.setContent { + QuickSettingsLayout( + brightness = { Brightness() }, + tiles = { Tiles(TILES_HEIGHT_PORTRAIT) }, + media = { Media() }, + mediaInRow = true, + ) + } + + composeTestRule.waitForIdle() + + val brightnessBounds = composeTestRule.onNodeWithTag(BRIGHTNESS).getBoundsInRoot() + val tilesBounds = composeTestRule.onNodeWithTag(TILES).getBoundsInRoot() + val mediaBounds = composeTestRule.onNodeWithTag(MEDIA).getBoundsInRoot() + + // Brightness takes full width, with left end aligned with tiles and right end aligned with + // media + assertThat(brightnessBounds.left).isEqualTo(tilesBounds.left) + assertThat(brightnessBounds.right).isEqualTo(mediaBounds.right) + + // Brightness above tiles and media + assertThat(brightnessBounds.bottom).isLessThan(tilesBounds.top) + assertThat(brightnessBounds.bottom).isLessThan(mediaBounds.top) + + // Media to the right of tiles + assertThat(tilesBounds.right).isLessThan(mediaBounds.left) + // "Same" width + assertThat((tilesBounds.width - mediaBounds.width).abs()).isAtMost(1.dp) + // Vertically centered + assertThat((tilesBounds.centerY - mediaBounds.centerY).abs()).isAtMost(1.dp) + } + + private companion object { + const val BRIGHTNESS = "brightness" + const val TILES = "tiles" + const val MEDIA = "media" + val TILES_HEIGHT_PORTRAIT = 300.dp + val TILES_HEIGHT_LANDSCAPE = 150.dp + val MEDIA_HEIGHT = 100.dp + val BRIGHTNESS_HEIGHT = 64.dp + + @Composable + fun Brightness() { + Box(modifier = Modifier.testTag(BRIGHTNESS).height(BRIGHTNESS_HEIGHT).fillMaxWidth()) + } + + @Composable + fun Tiles(height: Dp) { + Box(modifier = Modifier.testTag(TILES).height(height).fillMaxWidth()) + } + + @Composable + fun Media() { + Box(modifier = Modifier.testTag(MEDIA).height(MEDIA_HEIGHT).fillMaxWidth()) + } + + val DpRect.centerY: Dp + get() = (top + bottom) / 2 + + fun Dp.abs() = if (this > 0.dp) this else -this + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelTest.kt new file mode 100644 index 000000000000..635badac04f5 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelTest.kt @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.viewmodel + +import android.content.res.Configuration +import android.content.res.mainResources +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository +import com.android.systemui.flags.setFlagValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.lifecycle.activateIn +import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QQS +import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QS +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.shade.data.repository.shadeRepository +import com.android.systemui.shade.shared.flag.DualShade +import com.android.systemui.shade.shared.model.ShadeMode +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.runner.RunWith +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(ParameterizedAndroidJunit4::class) +@SmallTest +class MediaInRowInLandscapeViewModelTest(private val testData: TestData) : SysuiTestCase() { + + private val kosmos = testKosmos().apply { usingMediaInComposeFragment = testData.usingMedia } + + private val underTest by lazy { + kosmos.mediaInRowInLandscapeViewModelFactory.create(TESTED_MEDIA_LOCATION) + } + + @Before + fun setUp() { + mSetFlagsRule.setFlagValue(DualShade.FLAG_NAME, testData.shadeMode == ShadeMode.Dual) + } + + @Test + fun shouldMediaShowInRow() = + with(kosmos) { + testScope.runTest { + underTest.activateIn(testScope) + + shadeRepository.setShadeLayoutWide(testData.shadeMode != ShadeMode.Single) + val config = + Configuration(mainResources.configuration).apply { + orientation = testData.orientation + screenLayout = testData.screenLayoutLong + } + fakeConfigurationRepository.onConfigurationChange(config) + mainResources.configuration.updateFrom(config) + mediaHostStatesManager.updateHostState( + testData.mediaLocation, + MediaHost.MediaHostStateHolder().apply { visible = testData.mediaVisible }, + ) + runCurrent() + + assertThat(underTest.shouldMediaShowInRow).isEqualTo(testData.mediaInRowExpected) + } + } + + data class TestData( + val usingMedia: Boolean, + val shadeMode: ShadeMode, + val orientation: Int, + val screenLayoutLong: Int, + val mediaVisible: Boolean, + @MediaLocation val mediaLocation: Int, + ) { + val mediaInRowExpected: Boolean + get() = + usingMedia && + shadeMode == ShadeMode.Single && + orientation == Configuration.ORIENTATION_LANDSCAPE && + screenLayoutLong == Configuration.SCREENLAYOUT_LONG_YES && + mediaVisible && + mediaLocation == TESTED_MEDIA_LOCATION + } + + companion object { + private const val TESTED_MEDIA_LOCATION = LOCATION_QS + + @JvmStatic + @Parameters(name = "{0}") + fun data(): Collection<TestData> { + val usingMediaValues = setOf(true, false) + val shadeModeValues = setOf(ShadeMode.Single, ShadeMode.Split, ShadeMode.Dual) + val orientationValues = + setOf(Configuration.ORIENTATION_LANDSCAPE, Configuration.ORIENTATION_PORTRAIT) + val screenLayoutLongValues = + setOf(Configuration.SCREENLAYOUT_LONG_YES, Configuration.SCREENLAYOUT_LONG_NO) + val mediaVisibleValues = setOf(true, false) + val mediaLocationsValues = setOf(LOCATION_QS, LOCATION_QQS) + + return usingMediaValues.flatMap { usingMedia -> + shadeModeValues.flatMap { shadeMode -> + orientationValues.flatMap { orientation -> + screenLayoutLongValues.flatMap { screenLayoutLong -> + mediaVisibleValues.flatMap { mediaVisible -> + mediaLocationsValues.map { mediaLocation -> + TestData( + usingMedia, + shadeMode, + orientation, + screenLayoutLong, + mediaVisible, + mediaLocation, + ) + } + } + } + } + } + } + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelTest.kt new file mode 100644 index 000000000000..4ae8589de87b --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelTest.kt @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.viewmodel + +import android.content.res.mainResources +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.ui.data.repository.configurationRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.runCurrent +import com.android.systemui.kosmos.testCase +import com.android.systemui.kosmos.testScope +import com.android.systemui.lifecycle.activateIn +import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QQS +import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QS +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.panels.data.repository.QSColumnsRepository +import com.android.systemui.qs.panels.data.repository.qsColumnsRepository +import com.android.systemui.res.R +import com.android.systemui.shade.shared.flag.DualShade +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SmallTest +class QSColumnsViewModelTest : SysuiTestCase() { + private val kosmos = + testKosmos().apply { + usingMediaInComposeFragment = true + testCase.context.orCreateTestableResources.addOverride( + R.integer.quick_settings_infinite_grid_num_columns, + SINGLE_SPLIT_SHADE_COLUMNS, + ) + testCase.context.orCreateTestableResources.addOverride( + R.integer.quick_settings_dual_shade_num_columns, + DUAL_SHADE_COLUMNS, + ) + testCase.context.orCreateTestableResources.addOverride( + R.integer.quick_settings_split_shade_num_columns, + SINGLE_SPLIT_SHADE_COLUMNS, + ) + qsColumnsRepository = QSColumnsRepository(mainResources, configurationRepository) + } + + @Test + @DisableFlags(DualShade.FLAG_NAME) + fun mediaLocationNull_singleOrSplit_alwaysSingleShadeColumns() = + with(kosmos) { + testScope.runTest { + val underTest = qsColumnsViewModelFactory.create(null) + underTest.activateIn(testScope) + + setConfigurationForMediaInRow(mediaInRow = false) + + makeMediaVisible(LOCATION_QQS, visible = true) + makeMediaVisible(LOCATION_QS, visible = true) + runCurrent() + + assertThat(underTest.columns).isEqualTo(SINGLE_SPLIT_SHADE_COLUMNS) + + setConfigurationForMediaInRow(mediaInRow = true) + runCurrent() + + assertThat(underTest.columns).isEqualTo(SINGLE_SPLIT_SHADE_COLUMNS) + } + } + + @Test + @EnableFlags(DualShade.FLAG_NAME) + fun mediaLocationNull_dualShade_alwaysDualShadeColumns() = + with(kosmos) { + testScope.runTest { + val underTest = qsColumnsViewModelFactory.create(null) + underTest.activateIn(testScope) + + setConfigurationForMediaInRow(mediaInRow = false) + + makeMediaVisible(LOCATION_QQS, visible = true) + makeMediaVisible(LOCATION_QS, visible = true) + runCurrent() + + assertThat(underTest.columns).isEqualTo(DUAL_SHADE_COLUMNS) + + setConfigurationForMediaInRow(mediaInRow = true) + runCurrent() + + assertThat(underTest.columns).isEqualTo(DUAL_SHADE_COLUMNS) + } + } + + @Test + @EnableFlags(DualShade.FLAG_NAME) + fun mediaLocationQS_dualShade_alwaysDualShadeColumns() = + with(kosmos) { + testScope.runTest { + val underTest = qsColumnsViewModelFactory.create(LOCATION_QS) + underTest.activateIn(testScope) + + setConfigurationForMediaInRow(mediaInRow = false) + + makeMediaVisible(LOCATION_QS, visible = true) + runCurrent() + + assertThat(underTest.columns).isEqualTo(DUAL_SHADE_COLUMNS) + + setConfigurationForMediaInRow(mediaInRow = true) + runCurrent() + + assertThat(underTest.columns).isEqualTo(DUAL_SHADE_COLUMNS) + } + } + + @Test + @EnableFlags(DualShade.FLAG_NAME) + fun mediaLocationQQS_dualShade_alwaysDualShadeColumns() = + with(kosmos) { + testScope.runTest { + val underTest = qsColumnsViewModelFactory.create(LOCATION_QQS) + underTest.activateIn(testScope) + + setConfigurationForMediaInRow(mediaInRow = false) + + makeMediaVisible(LOCATION_QQS, visible = true) + runCurrent() + + assertThat(underTest.columns).isEqualTo(DUAL_SHADE_COLUMNS) + + setConfigurationForMediaInRow(mediaInRow = true) + runCurrent() + + assertThat(underTest.columns).isEqualTo(DUAL_SHADE_COLUMNS) + } + } + + @Test + @DisableFlags(DualShade.FLAG_NAME) + fun mediaLocationQS_singleOrSplit_halfColumnsOnCorrectConfigurationAndVisible() = + with(kosmos) { + testScope.runTest { + val underTest = qsColumnsViewModelFactory.create(LOCATION_QS) + underTest.activateIn(testScope) + + setConfigurationForMediaInRow(mediaInRow = false) + runCurrent() + + assertThat(underTest.columns).isEqualTo(SINGLE_SPLIT_SHADE_COLUMNS) + + setConfigurationForMediaInRow(mediaInRow = true) + runCurrent() + + assertThat(underTest.columns).isEqualTo(SINGLE_SPLIT_SHADE_COLUMNS) + + makeMediaVisible(LOCATION_QS, visible = true) + runCurrent() + + assertThat(underTest.columns).isEqualTo(SINGLE_SPLIT_SHADE_COLUMNS / 2) + } + } + + @Test + @DisableFlags(DualShade.FLAG_NAME) + fun mediaLocationQQS_singleOrSplit_halfColumnsOnCorrectConfigurationAndVisible() = + with(kosmos) { + testScope.runTest { + val underTest = qsColumnsViewModelFactory.create(LOCATION_QQS) + underTest.activateIn(testScope) + + setConfigurationForMediaInRow(mediaInRow = false) + runCurrent() + + assertThat(underTest.columns).isEqualTo(SINGLE_SPLIT_SHADE_COLUMNS) + + setConfigurationForMediaInRow(mediaInRow = true) + runCurrent() + + assertThat(underTest.columns).isEqualTo(SINGLE_SPLIT_SHADE_COLUMNS) + + makeMediaVisible(LOCATION_QQS, visible = true) + runCurrent() + + assertThat(underTest.columns).isEqualTo(SINGLE_SPLIT_SHADE_COLUMNS / 2) + } + } + + companion object { + private const val SINGLE_SPLIT_SHADE_COLUMNS = 4 + private const val DUAL_SHADE_COLUMNS = 2 + + private fun Kosmos.makeMediaVisible(@MediaLocation location: Int, visible: Boolean) { + mediaHostStatesManager.updateHostState( + location, + MediaHost.MediaHostStateHolder().apply { this.visible = visible }, + ) + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt index ab5a049c91f1..3d1265aec45e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt @@ -24,6 +24,11 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn +import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QQS +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.panels.domain.interactor.qsPreferencesInteractor import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor import com.android.systemui.qs.pipeline.shared.TileSpec @@ -67,6 +72,7 @@ class QuickQuickSettingsViewModelTest : SysuiTestCase() { 4, ) fakeConfigurationRepository.onConfigurationChange() + usingMediaInComposeFragment = true } private val underTest = @@ -145,6 +151,33 @@ class QuickQuickSettingsViewModelTest : SysuiTestCase() { } } + @Test + fun mediaVisibleInLandscape_doubleRows_halfColumns() = + with(kosmos) { + testScope.runTest { + setRows(1) + assertThat(underTest.columns).isEqualTo(4) + // All tiles in 4 columns (but we only show the first 3 tiles) + // [1] [2] [3 3] + // [4] [5 5] + // [6 6] [7] [8] + // [9 9] + + runCurrent() + assertThat(underTest.tileViewModels.map { it.tile.spec }).isEqualTo(tiles.take(3)) + + makeMediaVisible(LOCATION_QQS, visible = true) + setConfigurationForMediaInRow(mediaInRow = true) + runCurrent() + + assertThat(underTest.columns).isEqualTo(2) + // Tiles in 4 columns + // [1] [2] + // [3 3] + assertThat(underTest.tileViewModels.map { it.tile.spec }).isEqualTo(tiles.take(3)) + } + } + private fun Kosmos.setTiles(tiles: List<TileSpec>) { currentTilesInteractor.setTiles(tiles) } @@ -163,5 +196,12 @@ class QuickQuickSettingsViewModelTest : SysuiTestCase() { private companion object { const val PREFIX_SMALL = "small" const val PREFIX_LARGE = "large" + + private fun Kosmos.makeMediaVisible(@MediaLocation location: Int, visible: Boolean) { + mediaHostStatesManager.updateHostState( + location, + MediaHost.MediaHostStateHolder().apply { this.visible = visible }, + ) + } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt index f32894dfd383..24f6b6d8566b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt @@ -31,6 +31,7 @@ import com.android.systemui.lifecycle.activateIn import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.qs.composefragment.dagger.usingMediaInComposeFragment import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.domain.startable.sceneContainerStartable import com.android.systemui.scene.shared.model.Overlays @@ -55,7 +56,10 @@ import org.junit.runner.RunWith @EnableFlags(DualShade.FLAG_NAME) class QuickSettingsShadeOverlayContentViewModelTest : SysuiTestCase() { - private val kosmos = testKosmos() + private val kosmos = + testKosmos().apply { + usingMediaInComposeFragment = false // This is not for the compose fragment + } private val testScope = kosmos.testScope private val sceneInteractor = kosmos.sceneInteractor diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt index bacff99fe048..1bd0b246ad3f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt @@ -31,6 +31,7 @@ import android.widget.FrameLayout import androidx.activity.OnBackPressedDispatcher import androidx.activity.OnBackPressedDispatcherOwner import androidx.activity.setViewTreeOnBackPressedDispatcherOwner +import androidx.annotation.VisibleForTesting import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.tween @@ -41,13 +42,16 @@ import androidx.compose.foundation.ScrollState import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable @@ -59,6 +63,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.PointerEventPass @@ -75,7 +80,6 @@ import androidx.compose.ui.semantics.CustomAccessibilityAction import androidx.compose.ui.semantics.customActions import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.round import androidx.compose.ui.util.fastRoundToInt import androidx.compose.ui.viewinterop.AndroidView @@ -97,6 +101,7 @@ import com.android.compose.modifiers.padding import com.android.compose.modifiers.thenIf import com.android.compose.theme.PlatformTheme import com.android.systemui.Dumpable +import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.dump.DumpManager import com.android.systemui.lifecycle.repeatWhenAttached @@ -107,6 +112,7 @@ import com.android.systemui.plugins.qs.QSContainerController import com.android.systemui.qs.composefragment.SceneKeys.QuickQuickSettings import com.android.systemui.qs.composefragment.SceneKeys.QuickSettings import com.android.systemui.qs.composefragment.SceneKeys.toIdleSceneKey +import com.android.systemui.qs.composefragment.ui.GridAnchor import com.android.systemui.qs.composefragment.ui.NotificationScrimClipParams import com.android.systemui.qs.composefragment.ui.notificationScrimClip import com.android.systemui.qs.composefragment.ui.quickQuickSettingsToQuickSettings @@ -115,8 +121,8 @@ import com.android.systemui.qs.flags.QSComposeFragment import com.android.systemui.qs.footer.ui.compose.FooterActions import com.android.systemui.qs.panels.ui.compose.EditMode import com.android.systemui.qs.panels.ui.compose.QuickQuickSettings +import com.android.systemui.qs.panels.ui.compose.TileGrid import com.android.systemui.qs.shared.ui.ElementKeys -import com.android.systemui.qs.ui.composable.QuickSettingsLayout import com.android.systemui.qs.ui.composable.QuickSettingsShade import com.android.systemui.qs.ui.composable.QuickSettingsTheme import com.android.systemui.res.R @@ -299,7 +305,7 @@ constructor( transitions = transitions { from(QuickQuickSettings, QuickSettings) { - quickQuickSettingsToQuickSettings(viewModel::inFirstPage::get) + quickQuickSettingsToQuickSettings(viewModel::animateTilesExpansion::get) } }, ) @@ -596,8 +602,21 @@ constructor( } .padding(top = { qqsPadding }, bottom = { bottomPadding }) ) { + val Tiles = + @Composable { + QuickQuickSettings( + viewModel = viewModel.containerViewModel.quickQuickSettingsViewModel + ) + } + val Media = + @Composable { + if (viewModel.qqsMediaVisible) { + MediaObject(mediaHost = viewModel.qqsMediaHost) + } + } + if (viewModel.isQsEnabled) { - Column( + Box( modifier = Modifier.collapseExpandSemanticAction( stringResource( @@ -608,16 +627,13 @@ constructor( horizontal = { QuickSettingsShade.Dimensions.Padding.roundToPx() } - ), - verticalArrangement = - spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical)), + ) ) { - QuickQuickSettings( - viewModel = viewModel.containerViewModel.quickQuickSettingsViewModel + QuickQuickSettingsLayout( + tiles = Tiles, + media = Media, + mediaInRow = viewModel.qqsMediaInRow, ) - if (viewModel.qqsMediaVisible) { - MediaObject(mediaHost = viewModel.qqsMediaHost) - } } } } @@ -657,23 +673,58 @@ constructor( .verticalScroll(scrollState) .sysuiResTag(ResIdTags.qsScroll) ) { + val containerViewModel = viewModel.containerViewModel Spacer( modifier = Modifier.height { qqsPadding + qsExtraPadding.roundToPx() } ) - QuickSettingsLayout( - viewModel = viewModel.containerViewModel, - modifier = Modifier.sysuiResTag(ResIdTags.quickSettingsPanel), - ) - Spacer(modifier = Modifier.height(8.dp)) - if (viewModel.qsMediaVisible) { - MediaObject( - mediaHost = viewModel.qsMediaHost, - modifier = - Modifier.padding( - horizontal = { - QuickSettingsShade.Dimensions.Padding.roundToPx() - } - ), + val BrightnessSlider = + @Composable { + BrightnessSliderContainer( + viewModel = containerViewModel.brightnessSliderViewModel, + modifier = + Modifier.fillMaxWidth() + .height( + QuickSettingsShade.Dimensions.BrightnessSliderHeight + ), + ) + } + val TileGrid = + @Composable { + Box { + GridAnchor() + TileGrid( + viewModel = containerViewModel.tileGridViewModel, + modifier = + Modifier.fillMaxWidth() + .heightIn( + max = + QuickSettingsShade.Dimensions.GridMaxHeight + ), + containerViewModel.editModeViewModel::startEditing, + ) + } + } + val Media = + @Composable { + if (viewModel.qsMediaVisible) { + MediaObject(mediaHost = viewModel.qsMediaHost) + } + } + Box( + modifier = + Modifier.fillMaxWidth() + .sysuiResTag(ResIdTags.quickSettingsPanel) + .padding( + top = QuickSettingsShade.Dimensions.Padding, + start = QuickSettingsShade.Dimensions.Padding, + end = QuickSettingsShade.Dimensions.Padding, + ) + ) { + QuickSettingsLayout( + brightness = BrightnessSlider, + tiles = TileGrid, + media = Media, + mediaInRow = viewModel.qsMediaInRow, ) } } @@ -957,6 +1008,63 @@ private fun MediaObject(mediaHost: MediaHost, modifier: Modifier = Modifier) { } } +@Composable +@VisibleForTesting +fun QuickQuickSettingsLayout( + tiles: @Composable () -> Unit, + media: @Composable () -> Unit, + mediaInRow: Boolean, +) { + if (mediaInRow) { + Row( + horizontalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical)), + verticalAlignment = Alignment.CenterVertically, + ) { + Box(modifier = Modifier.weight(1f)) { tiles() } + Box(modifier = Modifier.weight(1f)) { media() } + } + } else { + Column(verticalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical))) { + tiles() + media() + } + } +} + +@Composable +@VisibleForTesting +fun QuickSettingsLayout( + brightness: @Composable () -> Unit, + tiles: @Composable () -> Unit, + media: @Composable () -> Unit, + mediaInRow: Boolean, +) { + if (mediaInRow) { + Column( + verticalArrangement = spacedBy(QuickSettingsShade.Dimensions.Padding), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + brightness() + Row( + horizontalArrangement = spacedBy(QuickSettingsShade.Dimensions.Padding), + verticalAlignment = Alignment.CenterVertically, + ) { + Box(modifier = Modifier.weight(1f)) { tiles() } + Box(modifier = Modifier.weight(1f)) { media() } + } + } + } else { + Column( + verticalArrangement = spacedBy(QuickSettingsShade.Dimensions.Padding), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + brightness() + tiles() + media() + } + } +} + private object ResIdTags { const val quickSettingsPanel = "quick_settings_panel" const val quickQsPanel = "quick_qs_panel" diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt index 512732090036..676f6a426264 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt @@ -19,6 +19,7 @@ package com.android.systemui.qs.composefragment.dagger import android.content.Context import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.qs.flags.QSComposeFragment import com.android.systemui.util.Utils import dagger.Module import dagger.Provides @@ -34,7 +35,7 @@ interface QSFragmentComposeModule { @SysUISingleton @Named(QS_USING_MEDIA_PLAYER) fun providesUsingMedia(@Application context: Context): Boolean { - return Utils.useQsMediaPlayer(context) + return QSComposeFragment.isEnabled && Utils.useQsMediaPlayer(context) } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/FromQuickQuickSettingsToQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/FromQuickQuickSettingsToQuickSettings.kt index 9e3945ecba57..c1a417411975 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/FromQuickQuickSettingsToQuickSettings.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/FromQuickQuickSettingsToQuickSettings.kt @@ -20,7 +20,9 @@ import com.android.compose.animation.scene.TransitionBuilder import com.android.systemui.qs.composefragment.SceneKeys import com.android.systemui.qs.shared.ui.ElementKeys -fun TransitionBuilder.quickQuickSettingsToQuickSettings(inFirstPage: () -> Boolean = { true }) { +fun TransitionBuilder.quickQuickSettingsToQuickSettings( + animateTilesExpansion: () -> Boolean = { true } +) { fractionRange(start = 0.5f) { fade(ElementKeys.QuickSettingsContent) } @@ -28,7 +30,7 @@ fun TransitionBuilder.quickQuickSettingsToQuickSettings(inFirstPage: () -> Boole anchoredTranslate(ElementKeys.QuickSettingsContent, ElementKeys.GridAnchor) - sharedElement(ElementKeys.TileElementMatcher, enabled = inFirstPage()) + sharedElement(ElementKeys.TileElementMatcher, enabled = animateTilesExpansion()) // This will animate between 0f (QQS) and 0.6, fading in the QQS tiles when coming back // from non first page QS. The QS content ends fading out at 0.5f, so there's a brief diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt index 0ca621d7d2e2..2f5d36580754 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt @@ -39,6 +39,8 @@ import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.lifecycle.Hydrator import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager +import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QQS +import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QS 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 @@ -49,6 +51,7 @@ import com.android.systemui.qs.composefragment.dagger.QSFragmentComposeModule import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel import com.android.systemui.qs.panels.domain.interactor.TileSquishinessInteractor import com.android.systemui.qs.panels.ui.viewmodel.InFirstPageViewModel +import com.android.systemui.qs.panels.ui.viewmodel.MediaInRowInLandscapeViewModel import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Scenes @@ -94,6 +97,7 @@ constructor( private val largeScreenHeaderHelper: LargeScreenHeaderHelper, private val squishinessInteractor: TileSquishinessInteractor, private val inFirstPageViewModel: InFirstPageViewModel, + mediaInRowInLandscapeViewModelFactory: MediaInRowInLandscapeViewModel.Factory, @Named(QUICK_QS_PANEL) val qqsMediaHost: MediaHost, @Named(QS_PANEL) val qsMediaHost: MediaHost, @Named(QSFragmentComposeModule.QS_USING_MEDIA_PLAYER) private val usingMedia: Boolean, @@ -101,6 +105,8 @@ constructor( ) : Dumpable, ExclusiveActivatable() { val containerViewModel = containerViewModelFactory.create(true) + private val qqsMediaInRowViewModel = mediaInRowInLandscapeViewModelFactory.create(LOCATION_QQS) + private val qsMediaInRowViewModel = mediaInRowInLandscapeViewModelFactory.create(LOCATION_QS) private val hydrator = Hydrator("QSFragmentComposeViewModel.hydrator") @@ -195,7 +201,7 @@ constructor( } } - val isQsFullyExpanded by derivedStateOf { expansionState.progress >= 1f } + val isQsFullyExpanded by derivedStateOf { expansionState.progress >= 1f && isQsExpanded } /** * Accessibility action for collapsing/expanding QS. The provided runnable is responsible for @@ -203,9 +209,6 @@ constructor( */ var collapseExpandAccessibilityAction: Runnable? = null - val inFirstPage: Boolean - get() = inFirstPageViewModel.inFirstPage - var overScrollAmount by mutableStateOf(0) val viewTranslationY by derivedStateOf { @@ -252,6 +255,9 @@ constructor( }, ) + val qqsMediaInRow: Boolean + get() = qqsMediaInRowViewModel.shouldMediaShowInRow + val qsMediaVisible by hydrator.hydratedStateOf( traceName = "qsMediaVisible", @@ -259,6 +265,18 @@ constructor( source = if (usingMedia) mediaHostVisible(qsMediaHost) else flowOf(false), ) + val qsMediaInRow: Boolean + get() = qsMediaInRowViewModel.shouldMediaShowInRow + + val animateTilesExpansion: Boolean + get() = inFirstPage && !mediaSuddenlyAppearingInLandscape + + private val inFirstPage: Boolean + get() = inFirstPageViewModel.inFirstPage + + private val mediaSuddenlyAppearingInLandscape: Boolean + get() = !qqsMediaInRow && qsMediaInRow + private var qsBounds by mutableStateOf(Rect()) private val constrainedSquishinessFraction: Float @@ -362,6 +380,8 @@ constructor( launch { hydrateSquishinessInteractor() } launch { hydrator.activate() } launch { containerViewModel.activate() } + launch { qqsMediaInRowViewModel.activate() } + launch { qsMediaInRowViewModel.activate() } awaitCancellation() } } @@ -391,6 +411,7 @@ constructor( println("isQSVisible", isQsVisible) println("isQSEnabled", isQsEnabled) println("isCustomizing", containerViewModel.editModeViewModel.isEditing.value) + println("inFirstPage", inFirstPage) } printSection("Expansion state") { println("qsExpansion", qsExpansion) @@ -423,7 +444,9 @@ constructor( } printSection("Media") { println("qqsMediaVisible", qqsMediaVisible) + println("qqsMediaInRow", qqsMediaInRow) println("qsMediaVisible", qsMediaVisible) + println("qsMediaInRow", qsMediaInRow) } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt index 43fd0f5feec7..1f55ac777de5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt @@ -35,8 +35,6 @@ import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout import com.android.systemui.qs.panels.ui.compose.infinitegrid.InfiniteGridLayout import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModelImpl -import com.android.systemui.qs.panels.ui.viewmodel.QSColumnsSizeViewModelImpl -import com.android.systemui.qs.panels.ui.viewmodel.QSColumnsViewModel import dagger.Binds import dagger.Module import dagger.Provides @@ -58,8 +56,6 @@ interface PanelsModule { @Binds fun bindIconTilesViewModel(impl: IconTilesViewModelImpl): IconTilesViewModel - @Binds fun bindQSColumnsViewModel(impl: QSColumnsSizeViewModelImpl): QSColumnsViewModel - @Binds @PaginatedBaseLayoutType fun bindPaginatedBaseGridLayout(impl: InfiniteGridLayout): PaginatableGridLayout diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt index 6cc2cbc63d09..2efe500912cd 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt @@ -73,7 +73,7 @@ constructor( tiles.forEach { it.startListening(token) } onDispose { tiles.forEach { it.stopListening(token) } } } - val columns by viewModel.columns + val columns = viewModel.columns val rows = viewModel.rows val pages = diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt index 19ab29e6c796..5ac2ad02d671 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt @@ -30,6 +30,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.grid.ui.compose.VerticalSpannedGrid import com.android.systemui.haptics.msdl.qs.TileHapticsViewModelFactoryProvider import com.android.systemui.lifecycle.rememberViewModel +import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QS import com.android.systemui.qs.panels.shared.model.SizedTileImpl import com.android.systemui.qs.panels.ui.compose.PaginatableGridLayout import com.android.systemui.qs.panels.ui.compose.bounceableInfo @@ -72,7 +73,12 @@ constructor( rememberViewModel(traceName = "InfiniteGridLayout.TileGrid") { viewModel.dynamicIconTilesViewModelFactory.create() } - val columns by viewModel.gridSizeViewModel.columns + val columnsWithMediaViewModel = + rememberViewModel(traceName = "InfiniteGridLAyout.TileGrid") { + viewModel.columnsWithMediaViewModelFactory.create(LOCATION_QS) + } + + val columns = columnsWithMediaViewModel.columns val sizedTiles = tiles.map { SizedTileImpl(it, it.spec.width()) } val bounceables = remember(sizedTiles) { List(sizedTiles.size) { BounceableTileViewModel() } } @@ -118,7 +124,11 @@ constructor( rememberViewModel(traceName = "InfiniteGridLayout.EditTileGrid") { viewModel.dynamicIconTilesViewModelFactory.create() } - val columns by viewModel.gridSizeViewModel.columns + val columnsViewModel = + rememberViewModel(traceName = "InfiniteGridLayout.EditTileGrid") { + viewModel.columnsWithMediaViewModelFactory.createWithoutMediaTracking() + } + val columns = columnsViewModel.columns val largeTiles by iconTilesViewModel.largeTiles.collectAsStateWithLifecycle() // Non-current tiles should always be displayed as icon tiles. diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridViewModel.kt index d68710048e13..3327141d2bc5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridViewModel.kt @@ -16,7 +16,6 @@ package com.android.systemui.qs.panels.ui.viewmodel -import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.qs.panels.ui.dialog.QSResetDialogDelegate import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -25,19 +24,15 @@ class InfiniteGridViewModel @AssistedInject constructor( val dynamicIconTilesViewModelFactory: DynamicIconTilesViewModel.Factory, - val gridSizeViewModel: QSColumnsViewModel, + val columnsWithMediaViewModelFactory: QSColumnsViewModel.Factory, val squishinessViewModel: TileSquishinessViewModel, private val resetDialogDelegate: QSResetDialogDelegate, -) : ExclusiveActivatable() { +) { fun showResetDialog() { resetDialogDelegate.showDialog() } - override suspend fun onActivated(): Nothing { - gridSizeViewModel.activate() - } - @AssistedFactory interface Factory { fun create(): InfiniteGridViewModel diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModel.kt new file mode 100644 index 000000000000..2ed8fd20df8f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModel.kt @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.viewmodel + +import android.content.res.Configuration +import android.content.res.Resources +import androidx.compose.runtime.getValue +import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.lifecycle.ExclusiveActivatable +import com.android.systemui.lifecycle.Hydrator +import com.android.systemui.media.controls.ui.controller.MediaHostStatesManager +import com.android.systemui.media.controls.ui.controller.MediaLocation +import com.android.systemui.media.controls.ui.view.MediaHostState +import com.android.systemui.qs.composefragment.dagger.QSFragmentComposeModule +import com.android.systemui.shade.domain.interactor.ShadeModeInteractor +import com.android.systemui.shade.shared.model.ShadeMode +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import javax.inject.Named +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart + +/** + * Indicates whether a particular UMO in [LOCATION_QQS] or [LOCATION_QS] should currently show in a + * row with the tiles, based on its visibility and device configuration. If the player is not + * visible, it will never indicate that media should show in row. + */ +class MediaInRowInLandscapeViewModel +@AssistedInject +constructor( + @Main resources: Resources, + configurationInteractor: ConfigurationInteractor, + shadeModeInteractor: ShadeModeInteractor, + private val mediaHostStatesManager: MediaHostStatesManager, + @Named(QSFragmentComposeModule.QS_USING_MEDIA_PLAYER) private val usingMedia: Boolean, + @Assisted @MediaLocation private val inLocation: Int, +) : ExclusiveActivatable() { + + private val hydrator = Hydrator("MediaInRowInLanscapeViewModel - $inLocation") + + val shouldMediaShowInRow: Boolean + get() = usingMedia && inSingleShade && isLandscapeAndLong && isMediaVisible + + private val inSingleShade: Boolean by + hydrator.hydratedStateOf( + traceName = "inSingleShade", + initialValue = shadeModeInteractor.shadeMode.value == ShadeMode.Single, + source = shadeModeInteractor.shadeMode.map { it == ShadeMode.Single }, + ) + + private val isLandscapeAndLong: Boolean by + hydrator.hydratedStateOf( + traceName = "isLandscapeAndLong", + initialValue = resources.configuration.isLandscapeAndLong, + source = configurationInteractor.configurationValues.map { it.isLandscapeAndLong }, + ) + + private val isMediaVisible by + hydrator.hydratedStateOf( + traceName = "isMediaVisible", + initialValue = false, + source = + conflatedCallbackFlow { + val callback = + object : MediaHostStatesManager.Callback { + override fun onHostStateChanged( + location: Int, + mediaHostState: MediaHostState, + ) { + if (location == inLocation) { + trySend(mediaHostState.visible) + } + } + } + mediaHostStatesManager.addCallback(callback) + + awaitClose { mediaHostStatesManager.removeCallback(callback) } + } + .onStart { + emit( + mediaHostStatesManager.mediaHostStates.get(inLocation)?.visible ?: false + ) + }, + ) + + override suspend fun onActivated(): Nothing { + hydrator.activate() + } + + @AssistedFactory + interface Factory { + fun create(@MediaLocation inLocation: Int): MediaInRowInLandscapeViewModel + } +} + +private val Configuration.isLandscapeAndLong: Boolean + get() = + orientation == Configuration.ORIENTATION_LANDSCAPE && + (screenLayout and Configuration.SCREENLAYOUT_LONG_MASK) == + Configuration.SCREENLAYOUT_LONG_YES diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt index 8bd9ed05c12c..e5607eb6e620 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt @@ -16,10 +16,10 @@ package com.android.systemui.qs.panels.ui.viewmodel -import androidx.compose.runtime.State 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 import com.android.systemui.qs.panels.domain.interactor.PaginatedGridInteractor import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -31,12 +31,13 @@ class PaginatedGridViewModel @AssistedInject constructor( iconTilesViewModel: IconTilesViewModel, - private val gridSizeViewModel: QSColumnsViewModel, + columnsWithMediaViewModelFactory: QSColumnsViewModel.Factory, paginatedGridInteractor: PaginatedGridInteractor, inFirstPageViewModel: InFirstPageViewModel, ) : IconTilesViewModel by iconTilesViewModel, ExclusiveActivatable() { private val hydrator = Hydrator("PaginatedGridViewModel") + private val columnsWithMediaViewModel = columnsWithMediaViewModelFactory.create(LOCATION_QS) val rows by hydrator.hydratedStateOf( @@ -47,13 +48,13 @@ constructor( var inFirstPage by inFirstPageViewModel::inFirstPage - val columns: State<Int> - get() = gridSizeViewModel.columns + val columns: Int + get() = columnsWithMediaViewModel.columns override suspend fun onActivated(): Nothing { coroutineScope { launch { hydrator.activate() } - launch { gridSizeViewModel.activate() } + launch { columnsWithMediaViewModel.activate() } awaitCancellation() } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModel.kt index 8926d2ff107e..85b7831fb270 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModel.kt @@ -16,25 +16,61 @@ package com.android.systemui.qs.panels.ui.viewmodel -import androidx.compose.runtime.State -import com.android.systemui.lifecycle.Activatable +import androidx.compose.runtime.derivedStateOf +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.MediaLocation import com.android.systemui.qs.panels.domain.interactor.QSColumnsInteractor -import javax.inject.Inject +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch -interface QSColumnsViewModel : Activatable { - val columns: State<Int> -} +/** + * View model for the number of columns that should be shown in a QS grid. + * * Create it with a [MediaLocation] to halve the number of columns when media should show in a row + * with the tiles. + * * Create it with a `null` [MediaLocation] to ignore media visibility (useful for edit mode). + */ +class QSColumnsViewModel +@AssistedInject +constructor( + interactor: QSColumnsInteractor, + mediaInRowInLandscapeViewModelFactory: MediaInRowInLandscapeViewModel.Factory, + @Assisted @MediaLocation mediaLocation: Int?, +) : ExclusiveActivatable() { + + private val hydrator = Hydrator("QSColumnsViewModelWithMedia") + + val columns by derivedStateOf { + if (mediaInRowInLandscapeViewModel?.shouldMediaShowInRow == true) { + columnsWithoutMedia / 2 + } else { + columnsWithoutMedia + } + } -class QSColumnsSizeViewModelImpl @Inject constructor(interactor: QSColumnsInteractor) : - QSColumnsViewModel, ExclusiveActivatable() { - private val hydrator = Hydrator("QSColumnsSizeViewModelImpl") + private val mediaInRowInLandscapeViewModel = + mediaLocation?.let { mediaInRowInLandscapeViewModelFactory.create(it) } - override val columns = - hydrator.hydratedStateOf(traceName = "columns", source = interactor.columns) + private val columnsWithoutMedia by + hydrator.hydratedStateOf(traceName = "columnsWithoutMedia", source = interactor.columns) override suspend fun onActivated(): Nothing { - hydrator.activate() + coroutineScope { + launch { hydrator.activate() } + launch { mediaInRowInLandscapeViewModel?.activate() } + awaitCancellation() + } + } + + @AssistedFactory + interface Factory { + fun create(mediaLocation: Int?): QSColumnsViewModel + + fun createWithoutMediaTracking() = create(null) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt index 0859c86d74e1..33ce5519b68c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt @@ -21,6 +21,7 @@ import androidx.compose.runtime.getValue import com.android.systemui.haptics.msdl.qs.TileHapticsViewModelFactoryProvider import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.lifecycle.Hydrator +import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QQS import com.android.systemui.qs.panels.domain.interactor.QuickQuickSettingsRowInteractor import com.android.systemui.qs.panels.shared.model.SizedTileImpl import com.android.systemui.qs.panels.shared.model.splitInRowsSequence @@ -36,23 +37,35 @@ class QuickQuickSettingsViewModel @AssistedInject constructor( tilesInteractor: CurrentTilesInteractor, - private val qsColumnsViewModel: QSColumnsViewModel, + qsColumnsViewModelFactory: QSColumnsViewModel.Factory, quickQuickSettingsRowInteractor: QuickQuickSettingsRowInteractor, + mediaInRowInLandscapeViewModelFactory: MediaInRowInLandscapeViewModel.Factory, val squishinessViewModel: TileSquishinessViewModel, iconTilesViewModel: IconTilesViewModel, val tileHapticsViewModelFactoryProvider: TileHapticsViewModelFactoryProvider, ) : ExclusiveActivatable() { private val hydrator = Hydrator("QuickQuickSettingsViewModel") + private val qsColumnsViewModel = qsColumnsViewModelFactory.create(LOCATION_QQS) + private val mediaInRowViewModel = mediaInRowInLandscapeViewModelFactory.create(LOCATION_QQS) - val columns by qsColumnsViewModel.columns + val columns: Int + get() = qsColumnsViewModel.columns private val largeTiles by hydrator.hydratedStateOf(traceName = "largeTiles", source = iconTilesViewModel.largeTiles) - private val rows by + private val rows: Int + get() = + if (mediaInRowViewModel.shouldMediaShowInRow) { + rowsWithoutMedia * 2 + } else { + rowsWithoutMedia + } + + private val rowsWithoutMedia by hydrator.hydratedStateOf( - traceName = "rows", + traceName = "rowsWithoutMedia", initialValue = quickQuickSettingsRowInteractor.defaultRows, source = quickQuickSettingsRowInteractor.rows, ) @@ -73,6 +86,7 @@ constructor( coroutineScope { launch { hydrator.activate() } launch { qsColumnsViewModel.activate() } + launch { mediaInRowViewModel.activate() } awaitCancellation() } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt index bda3192085ed..4ed491233f3c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt @@ -29,6 +29,7 @@ import com.android.systemui.qs.footerActionsController import com.android.systemui.qs.footerActionsViewModelFactory import com.android.systemui.qs.panels.domain.interactor.tileSquishinessInteractor import com.android.systemui.qs.panels.ui.viewmodel.inFirstPageViewModel +import com.android.systemui.qs.panels.ui.viewmodel.mediaInRowInLandscapeViewModelFactory import com.android.systemui.qs.ui.viewmodel.quickSettingsContainerViewModelFactory import com.android.systemui.shade.largeScreenHeaderHelper import com.android.systemui.shade.transition.largeScreenShadeInterpolator @@ -57,6 +58,7 @@ val Kosmos.qsFragmentComposeViewModelFactory by largeScreenHeaderHelper, tileSquishinessInteractor, inFirstPageViewModel, + mediaInRowInLandscapeViewModelFactory, qqsMediaHost, qsMediaHost, usingMediaInComposeFragment, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridViewModelKosmos.kt index 7613ea31c622..57aa20ae5f02 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridViewModelKosmos.kt @@ -25,7 +25,7 @@ val Kosmos.infiniteGridViewModelFactory by override fun create(): InfiniteGridViewModel { return InfiniteGridViewModel( dynamicIconTilesViewModelFactory, - qsColumnsViewModel, + qsColumnsViewModelFactory, tileSquishinessViewModel, qsResetDialogDelegateKosmos, ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelKosmos.kt new file mode 100644 index 000000000000..d1b613fe7f6e --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelKosmos.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.viewmodel + +import android.content.res.Configuration +import android.content.res.mainResources +import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository +import com.android.systemui.common.ui.domain.interactor.configurationInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.media.controls.ui.controller.mediaHostStatesManager +import com.android.systemui.qs.composefragment.dagger.usingMediaInComposeFragment +import com.android.systemui.shade.data.repository.shadeRepository +import com.android.systemui.shade.domain.interactor.shadeModeInteractor + +val Kosmos.mediaInRowInLandscapeViewModelFactory by + Kosmos.Fixture { + object : MediaInRowInLandscapeViewModel.Factory { + override fun create(inLocation: Int): MediaInRowInLandscapeViewModel { + return MediaInRowInLandscapeViewModel( + mainResources, + configurationInteractor, + shadeModeInteractor, + mediaHostStatesManager, + usingMediaInComposeFragment, + inLocation, + ) + } + } + } + +fun Kosmos.setConfigurationForMediaInRow(mediaInRow: Boolean) { + shadeRepository.setShadeLayoutWide(!mediaInRow) // media in row only in non wide + val config = + Configuration(mainResources.configuration).apply { + orientation = + if (mediaInRow) { + Configuration.ORIENTATION_LANDSCAPE + } else { + Configuration.ORIENTATION_PORTRAIT + } + screenLayout = Configuration.SCREENLAYOUT_LONG_YES + } + mainResources.configuration.updateFrom(config) + fakeConfigurationRepository.onConfigurationChange(config) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt index 5c8ca83ff2ae..0e5edb75846d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt @@ -23,7 +23,7 @@ val Kosmos.paginatedGridViewModel by Kosmos.Fixture { PaginatedGridViewModel( iconTilesViewModel, - qsColumnsViewModel, + qsColumnsViewModelFactory, paginatedGridInteractor, inFirstPageViewModel, ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelKosmos.kt index 16b2f5438797..d63b1b0b4224 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelKosmos.kt @@ -19,4 +19,15 @@ package com.android.systemui.qs.panels.ui.viewmodel import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.panels.domain.interactor.qsColumnsInteractor -val Kosmos.qsColumnsViewModel by Kosmos.Fixture { QSColumnsSizeViewModelImpl(qsColumnsInteractor) } +val Kosmos.qsColumnsViewModelFactory by + Kosmos.Fixture { + object : QSColumnsViewModel.Factory { + override fun create(mediaLocation: Int?): QSColumnsViewModel { + return QSColumnsViewModel( + qsColumnsInteractor, + mediaInRowInLandscapeViewModelFactory, + mediaLocation, + ) + } + } + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt index 20be5c675851..81beb20706db 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt @@ -27,8 +27,9 @@ val Kosmos.quickQuickSettingsViewModelFactory by override fun create(): QuickQuickSettingsViewModel { return QuickQuickSettingsViewModel( currentTilesInteractor, - qsColumnsViewModel, + qsColumnsViewModelFactory, quickQuickSettingsRowInteractor, + mediaInRowInLandscapeViewModelFactory, tileSquishinessViewModel, iconTilesViewModel, tileHapticsViewModelFactoryProvider, |