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

Commit 55e4b4a0 authored by Justin Weir's avatar Justin Weir Committed by Android (Google) Code Review
Browse files

Merge "Make QS switch to Split Shade on unfold" into main

parents 9e17c632 b4318f1d
Loading
Loading
Loading
Loading
+25 −0
Original line number Original line Diff line number Diff line
@@ -16,6 +16,7 @@


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


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


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


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


    private lateinit var underTest: QuickSettingsSceneContentViewModel
    private lateinit var underTest: QuickSettingsSceneContentViewModel


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


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


            assertThat(isMediaVisible).isTrue()
            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 Original line Diff line number Diff line
@@ -17,16 +17,24 @@
package com.android.systemui.qs.ui.viewmodel
package com.android.systemui.qs.ui.viewmodel


import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleOwner
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
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.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 com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.assisted.AssistedInject
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicBoolean
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch


/**
/**
 * Models UI state needed for rendering the content of the quick settings scene.
 * 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 footerActionsViewModelFactory: FooterActionsViewModel.Factory,
    private val footerActionsController: FooterActionsController,
    private val footerActionsController: FooterActionsController,
    val mediaCarouselInteractor: MediaCarouselInteractor,
    val mediaCarouselInteractor: MediaCarouselInteractor,
) {
    private val shadeInteractor: ShadeInteractor,
    private val sceneInteractor: SceneInteractor,
) : ExclusiveActivatable() {


    val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasAnyMediaOrRecommendation
    val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasAnyMediaOrRecommendation


@@ -56,6 +66,19 @@ constructor(
        return footerActionsViewModelFactory.create(lifecycleOwner)
        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
    @AssistedFactory
    interface Factory {
    interface Factory {
        fun create(): QuickSettingsSceneContentViewModel
        fun create(): QuickSettingsSceneContentViewModel
+13 −18
Original line number Original line Diff line number Diff line
@@ -58,29 +58,20 @@ constructor(


    fun onSceneChange(from: SceneKey, to: SceneKey) {
    fun onSceneChange(from: SceneKey, to: SceneKey) {
        check(from != to) { "from == to, from=${from.debugName}, to=${to.debugName}" }
        check(from != to) { "from == to, from=${from.debugName}, to=${to.debugName}" }
        when (stackOperation(from, to)) {

            Clear -> {
        _backStack.update { stack ->
                _backStack.value = sceneStackOf()
            when (stackOperation(from, to, stack)) {
            }
                null -> stack
            Push -> {
                Clear -> sceneStackOf()
                _backStack.update { s -> s.push(from) }
                Push -> stack.push(from)
            }
                Pop ->
            Pop -> {
                    checkNotNull(stack.pop()) { "Cannot pop ${from.debugName} when stack is empty" }
                _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}"
                            }
                        }
                }
            }
            }
        }
        }
        logger.logSceneBackStack(backStack.value.asIterable())
        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 =
        val fromDistance =
            checkNotNull(sceneContainerConfig.navigationDistances[from]) {
            checkNotNull(sceneContainerConfig.navigationDistances[from]) {
                "No distance mapping for scene \"${from.debugName}\"!"
                "No distance mapping for scene \"${from.debugName}\"!"
@@ -93,6 +84,7 @@ constructor(
        return when {
        return when {
            toDistance == 0 -> Clear
            toDistance == 0 -> Clear
            toDistance > fromDistance -> Push
            toDistance > fromDistance -> Push
            stack.peek() != to -> null
            toDistance < fromDistance -> Pop
            toDistance < fromDistance -> Pop
            else ->
            else ->
                error(
                error(
@@ -103,7 +95,10 @@ constructor(
    }
    }


    private sealed interface StackOperation
    private sealed interface StackOperation

    private data object Clear : StackOperation
    private data object Clear : StackOperation

    private data object Push : StackOperation
    private data object Push : StackOperation

    private data object Pop : StackOperation
    private data object Pop : StackOperation
}
}