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

Commit b4318f1d authored by Justin Weir's avatar Justin Weir
Browse files

Make QS switch to Split Shade on unfold

This aligns the scene container implementation of the shade with the
legacy implementation to fix b/360102562.

Fixes: 360102562
Test: expand shade, unfold, expand shade, fold
Test: added new test
Flag: com.android.systemui.scene_container
Change-Id: I70f1c1719d6879713ceb501b09eed03544ba7e72
parent 59292a2f
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

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

import android.platform.test.annotations.DisableFlags
import android.testing.TestableLooper.RunWithLooper
import androidx.lifecycle.LifecycleOwner
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -26,20 +27,26 @@ import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.domain.startable.sceneContainerStartable
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModelFactory
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.ui.viewmodel.shadeHeaderViewModelFactory
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -51,6 +58,7 @@ import org.mockito.Mockito.verify
@RunWith(AndroidJUnit4::class)
@RunWithLooper
@EnableSceneContainer
@DisableFlags(com.android.systemui.Flags.FLAG_DUAL_SHADE)
class QuickSettingsSceneContentViewModelTest : SysuiTestCase() {

    private val kosmos = testKosmos()
@@ -64,6 +72,8 @@ class QuickSettingsSceneContentViewModelTest : SysuiTestCase() {
    private val footerActionsController = mock<FooterActionsController>()

    private val sceneContainerStartable = kosmos.sceneContainerStartable
    private val sceneInteractor by lazy { kosmos.sceneInteractor }
    private val shadeInteractor by lazy { kosmos.shadeInteractor }

    private lateinit var underTest: QuickSettingsSceneContentViewModel

@@ -80,7 +90,10 @@ class QuickSettingsSceneContentViewModelTest : SysuiTestCase() {
                footerActionsViewModelFactory = footerActionsViewModelFactory,
                footerActionsController = footerActionsController,
                mediaCarouselInteractor = kosmos.mediaCarouselInteractor,
                shadeInteractor = shadeInteractor,
                sceneInteractor = sceneInteractor,
            )
        underTest.activateIn(testScope)
    }

    @Test
@@ -122,4 +135,16 @@ class QuickSettingsSceneContentViewModelTest : SysuiTestCase() {

            assertThat(isMediaVisible).isTrue()
        }

    @Test
    fun shadeModeChange_switchToShadeScene() =
        testScope.runTest {
            val scene by collectLastValue(sceneInteractor.currentScene)

            // switch to split shade
            kosmos.shadeRepository.setShadeLayoutWide(true)
            runCurrent()

            assertThat(scene).isEqualTo(Scenes.Shade)
        }
}
+24 −1
Original line number Diff line number Diff line
@@ -17,16 +17,24 @@
package com.android.systemui.qs.ui.viewmodel

import androidx.lifecycle.LifecycleOwner
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import java.util.concurrent.atomic.AtomicBoolean
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch

/**
 * Models UI state needed for rendering the content of the quick settings scene.
@@ -43,7 +51,9 @@ constructor(
    private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
    private val footerActionsController: FooterActionsController,
    val mediaCarouselInteractor: MediaCarouselInteractor,
) {
    private val shadeInteractor: ShadeInteractor,
    private val sceneInteractor: SceneInteractor,
) : ExclusiveActivatable() {

    val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasAnyMediaOrRecommendation

@@ -56,6 +66,19 @@ constructor(
        return footerActionsViewModelFactory.create(lifecycleOwner)
    }

    override suspend fun onActivated(): Nothing {
        coroutineScope {
            launch {
                shadeInteractor.shadeMode.collect { shadeMode ->
                    if (shadeMode == ShadeMode.Split) {
                        sceneInteractor.snapToScene(Scenes.Shade, "Unfold while on QS")
                    }
                }
            }
            awaitCancellation()
        }
    }

    @AssistedFactory
    interface Factory {
        fun create(): QuickSettingsSceneContentViewModel
+13 −18
Original line number Diff line number Diff line
@@ -58,29 +58,20 @@ constructor(

    fun onSceneChange(from: SceneKey, to: SceneKey) {
        check(from != to) { "from == to, from=${from.debugName}, to=${to.debugName}" }
        when (stackOperation(from, to)) {
            Clear -> {
                _backStack.value = sceneStackOf()
            }
            Push -> {
                _backStack.update { s -> s.push(from) }
            }
            Pop -> {
                _backStack.update { s ->
                    checkNotNull(s.pop()) { "Cannot pop ${from.debugName} when stack is empty" }
                        .also {
                            val popped = s.peek()
                            check(popped == to) {
                                "Expected to pop ${to.debugName} but instead popped ${popped?.debugName}"
                            }
                        }
                }

        _backStack.update { stack ->
            when (stackOperation(from, to, stack)) {
                null -> stack
                Clear -> sceneStackOf()
                Push -> stack.push(from)
                Pop ->
                    checkNotNull(stack.pop()) { "Cannot pop ${from.debugName} when stack is empty" }
            }
        }
        logger.logSceneBackStack(backStack.value.asIterable())
    }

    private fun stackOperation(from: SceneKey, to: SceneKey): StackOperation {
    private fun stackOperation(from: SceneKey, to: SceneKey, stack: SceneStack): StackOperation? {
        val fromDistance =
            checkNotNull(sceneContainerConfig.navigationDistances[from]) {
                "No distance mapping for scene \"${from.debugName}\"!"
@@ -93,6 +84,7 @@ constructor(
        return when {
            toDistance == 0 -> Clear
            toDistance > fromDistance -> Push
            stack.peek() != to -> null
            toDistance < fromDistance -> Pop
            else ->
                error(
@@ -103,7 +95,10 @@ constructor(
    }

    private sealed interface StackOperation

    private data object Clear : StackOperation

    private data object Push : StackOperation

    private data object Pop : StackOperation
}