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

Commit 65ae124c authored by Danny Burakov's avatar Danny Burakov Committed by Android (Google) Code Review
Browse files

Merge "[flexiglass] Hide overlays whenever we attempt to change scenes." into main

parents 5861e52b c9f4fe7b
Loading
Loading
Loading
Loading
+117 −70
Original line number Diff line number Diff line
@@ -23,7 +23,6 @@ import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.ObservableTransitionState.Transition.ShowOrHideOverlay
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
@@ -31,9 +30,8 @@ import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.scene.data.repository.Idle
import com.android.systemui.scene.data.repository.Transition
import com.android.systemui.scene.data.repository.sceneContainerRepository
@@ -56,8 +54,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -71,9 +67,7 @@ import org.mockito.kotlin.verify
@EnableSceneContainer
class SceneInteractorTest : SysuiTestCase() {

    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
    private val fakeSceneDataSource by lazy { kosmos.fakeSceneDataSource }
    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
    private val underTest by lazy { kosmos.sceneInteractor }

    @Before
@@ -93,7 +87,7 @@ class SceneInteractorTest : SysuiTestCase() {

    @Test
    fun changeScene_toUnknownScene_doesNothing() =
        testScope.runTest {
        kosmos.runTest {
            val currentScene by collectLastValue(underTest.currentScene)
            val unknownScene = SceneKey("UNKNOWN")
            val previousScene = currentScene
@@ -104,7 +98,7 @@ class SceneInteractorTest : SysuiTestCase() {

    @Test
    fun changeScene() =
        testScope.runTest {
        kosmos.runTest {
            val currentScene by collectLastValue(underTest.currentScene)
            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)

@@ -112,16 +106,45 @@ class SceneInteractorTest : SysuiTestCase() {
            assertThat(currentScene).isEqualTo(Scenes.Shade)
        }

    @Test
    fun changeScene_sameScene_hidesOverlays() =
        kosmos.runTest {
            kosmos.enableDualShade()
            val currentScene by collectLastValue(underTest.currentScene)
            val currentOverlays by collectLastValue(underTest.currentOverlays)
            underTest.showOverlay(Overlays.NotificationsShade, "reason")
            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
            assertThat(currentOverlays).isNotEmpty()

            underTest.changeScene(Scenes.Lockscreen, "reason")
            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
            assertThat(currentOverlays).isEmpty()
        }

    @Test
    fun changeScene_sameScene_requestToKeepOverlays_keepsOverlays() =
        kosmos.runTest {
            kosmos.enableDualShade()
            val currentScene by collectLastValue(underTest.currentScene)
            val currentOverlays by collectLastValue(underTest.currentOverlays)
            underTest.showOverlay(Overlays.NotificationsShade, "reason")
            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
            assertThat(currentOverlays).containsExactly(Overlays.NotificationsShade)

            underTest.changeScene(Scenes.Lockscreen, "reason", hideAllOverlays = false)
            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
            assertThat(currentOverlays).containsExactly(Overlays.NotificationsShade)
        }

    @Test
    fun changeScene_toGoneWhenUnl_doesNotThrow() =
        testScope.runTest {
        kosmos.runTest {
            val currentScene by collectLastValue(underTest.currentScene)
            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)

            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
                SuccessFingerprintAuthenticationStatus(0, true)
            )
            runCurrent()

            underTest.changeScene(Scenes.Gone, "reason")
            assertThat(currentScene).isEqualTo(Scenes.Gone)
@@ -129,11 +152,11 @@ class SceneInteractorTest : SysuiTestCase() {

    @Test(expected = IllegalStateException::class)
    fun changeScene_toGoneWhenStillLocked_throws() =
        testScope.runTest { underTest.changeScene(Scenes.Gone, "reason") }
        kosmos.runTest { underTest.changeScene(Scenes.Gone, "reason") }

    @Test
    fun changeScene_toGoneWhenTransitionToLockedFromGone() =
        testScope.runTest {
        kosmos.runTest {
            val currentScene by collectLastValue(underTest.currentScene)
            val transitionTo by collectLastValue(underTest.transitioningTo)
            kosmos.sceneContainerRepository.setTransitionState(
@@ -157,69 +180,113 @@ class SceneInteractorTest : SysuiTestCase() {

    @Test
    fun changeScene_toHomeSceneFamily() =
        testScope.runTest {
        kosmos.runTest {
            val currentScene by collectLastValue(underTest.currentScene)

            underTest.changeScene(SceneFamilies.Home, "reason")
            runCurrent()

            assertThat(currentScene).isEqualTo(kosmos.homeSceneFamilyResolver.resolvedScene.value)
        }

    @Test
    fun snapToScene_toUnknownScene_doesNothing() =
        testScope.runTest {
    fun snapToScene_toUnknownScene_doesNotChangeScene() =
        kosmos.runTest {
            val currentScene by collectLastValue(underTest.currentScene)
            val previousScene = currentScene
            val unknownScene = SceneKey("UNKNOWN")
            assertThat(previousScene).isNotEqualTo(unknownScene)
            underTest.snapToScene(unknownScene, loggingReason = "reason")
            assertThat(currentScene).isEqualTo(previousScene)
        }

    @Test
    fun snapToScene_toUnknownScene_hidesOverlays() =
        kosmos.runTest {
            kosmos.enableDualShade()
            val currentScene by collectLastValue(underTest.currentScene)
            val currentOverlays by collectLastValue(underTest.currentOverlays)
            underTest.showOverlay(Overlays.NotificationsShade, "reason")
            val previousScene = currentScene
            val unknownScene = SceneKey("UNKNOWN")
            assertThat(previousScene).isNotEqualTo(unknownScene)
            underTest.snapToScene(unknownScene, "reason")
            assertThat(currentOverlays).isNotEmpty()

            underTest.snapToScene(unknownScene, loggingReason = "reason")
            assertThat(currentScene).isEqualTo(previousScene)
            assertThat(currentOverlays).isEmpty()
        }

    @Test
    fun snapToScene() =
        testScope.runTest {
        kosmos.runTest {
            val currentScene by collectLastValue(underTest.currentScene)
            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)

            underTest.snapToScene(Scenes.Shade, "reason")
            underTest.snapToScene(Scenes.Shade, loggingReason = "reason")
            assertThat(currentScene).isEqualTo(Scenes.Shade)
        }

    @Test
    fun snapToScene_sameScene_hidesOverlays() =
        kosmos.runTest {
            kosmos.enableDualShade()
            val currentScene by collectLastValue(underTest.currentScene)
            val currentOverlays by collectLastValue(underTest.currentOverlays)
            underTest.showOverlay(Overlays.NotificationsShade, "reason")
            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
            assertThat(currentOverlays).isNotEmpty()

            underTest.snapToScene(Scenes.Lockscreen, loggingReason = "reason")
            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
            assertThat(currentOverlays).isEmpty()
        }

    @Test
    fun snapToScene_sameScene_requestToKeepOverlays_keepsOverlays() =
        kosmos.runTest {
            kosmos.enableDualShade()
            val currentScene by collectLastValue(underTest.currentScene)
            val currentOverlays by collectLastValue(underTest.currentOverlays)
            underTest.showOverlay(Overlays.NotificationsShade, "reason")
            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
            assertThat(currentOverlays).containsExactly(Overlays.NotificationsShade)

            underTest.snapToScene(Scenes.Lockscreen, "reason", hideAllOverlays = false)
            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
            assertThat(currentOverlays).containsExactly(Overlays.NotificationsShade)
        }

    @Test
    fun snapToScene_toGoneWhenUnl_doesNotThrow() =
        testScope.runTest {
        kosmos.runTest {
            val currentScene by collectLastValue(underTest.currentScene)
            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)

            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
                SuccessFingerprintAuthenticationStatus(0, true)
            )
            runCurrent()

            underTest.snapToScene(Scenes.Gone, "reason")
            underTest.snapToScene(Scenes.Gone, loggingReason = "reason")
            assertThat(currentScene).isEqualTo(Scenes.Gone)
        }

    @Test(expected = IllegalStateException::class)
    fun snapToScene_toGoneWhenStillLocked_throws() =
        testScope.runTest { underTest.snapToScene(Scenes.Gone, "reason") }
        kosmos.runTest { underTest.snapToScene(Scenes.Gone, loggingReason = "reason") }

    @Test
    fun snapToScene_toHomeSceneFamily() =
        testScope.runTest {
        kosmos.runTest {
            val currentScene by collectLastValue(underTest.currentScene)

            underTest.snapToScene(SceneFamilies.Home, "reason")
            runCurrent()
            underTest.snapToScene(SceneFamilies.Home, loggingReason = "reason")

            assertThat(currentScene).isEqualTo(kosmos.homeSceneFamilyResolver.resolvedScene.value)
        }

    @Test
    fun sceneChanged_inDataSource() =
        testScope.runTest {
        kosmos.runTest {
            val currentScene by collectLastValue(underTest.currentScene)
            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)

@@ -230,7 +297,7 @@ class SceneInteractorTest : SysuiTestCase() {

    @Test
    fun transitionState() =
        testScope.runTest {
        kosmos.runTest {
            val sceneContainerRepository = kosmos.sceneContainerRepository
            val transitionState =
                MutableStateFlow<ObservableTransitionState>(
@@ -268,7 +335,7 @@ class SceneInteractorTest : SysuiTestCase() {

    @Test
    fun transitioningTo_sceneChange() =
        testScope.runTest {
        kosmos.runTest {
            val transitionState =
                MutableStateFlow<ObservableTransitionState>(
                    ObservableTransitionState.Idle(underTest.currentScene.value)
@@ -307,7 +374,6 @@ class SceneInteractorTest : SysuiTestCase() {
    fun transitioningTo_overlayChange() =
        kosmos.runTest {
            enableDualShade()
            runCurrent()
            val transitionState =
                MutableStateFlow<ObservableTransitionState>(
                    ObservableTransitionState.Idle(underTest.currentScene.value)
@@ -352,7 +418,7 @@ class SceneInteractorTest : SysuiTestCase() {

    @Test
    fun isTransitionUserInputOngoing_idle_false() =
        testScope.runTest {
        kosmos.runTest {
            val transitionState =
                MutableStateFlow<ObservableTransitionState>(
                    ObservableTransitionState.Idle(Scenes.Shade)
@@ -366,7 +432,7 @@ class SceneInteractorTest : SysuiTestCase() {

    @Test
    fun isTransitionUserInputOngoing_transition_true() =
        testScope.runTest {
        kosmos.runTest {
            val transitionState =
                MutableStateFlow<ObservableTransitionState>(
                    ObservableTransitionState.Transition(
@@ -387,7 +453,7 @@ class SceneInteractorTest : SysuiTestCase() {

    @Test
    fun isTransitionUserInputOngoing_updateMidTransition_false() =
        testScope.runTest {
        kosmos.runTest {
            val transitionState =
                MutableStateFlow<ObservableTransitionState>(
                    ObservableTransitionState.Transition(
@@ -420,7 +486,7 @@ class SceneInteractorTest : SysuiTestCase() {

    @Test
    fun isTransitionUserInputOngoing_updateOnIdle_false() =
        testScope.runTest {
        kosmos.runTest {
            val transitionState =
                MutableStateFlow<ObservableTransitionState>(
                    ObservableTransitionState.Transition(
@@ -445,7 +511,7 @@ class SceneInteractorTest : SysuiTestCase() {

    @Test
    fun isVisible() =
        testScope.runTest {
        kosmos.runTest {
            val isVisible by collectLastValue(underTest.isVisible)
            assertThat(isVisible).isTrue()

@@ -458,7 +524,7 @@ class SceneInteractorTest : SysuiTestCase() {

    @Test
    fun isVisible_duringRemoteUserInteraction_forcedVisible() =
        testScope.runTest {
        kosmos.runTest {
            underTest.setVisible(false, "reason")
            val isVisible by collectLastValue(underTest.isVisible)
            assertThat(isVisible).isFalse()
@@ -472,21 +538,21 @@ class SceneInteractorTest : SysuiTestCase() {

    @Test
    fun resolveSceneFamily_home() =
        testScope.runTest {
        kosmos.runTest {
            assertThat(underTest.resolveSceneFamily(SceneFamilies.Home).first())
                .isEqualTo(kosmos.homeSceneFamilyResolver.resolvedScene.value)
        }

    @Test
    fun resolveSceneFamily_nonFamily() =
        testScope.runTest {
        kosmos.runTest {
            val resolved = underTest.resolveSceneFamily(Scenes.Gone).toList()
            assertThat(resolved).containsExactly(Scenes.Gone).inOrder()
        }

    @Test
    fun transitionValue_test_idle() =
        testScope.runTest {
        kosmos.runTest {
            val transitionValue by collectLastValue(underTest.transitionProgress(Scenes.Gone))

            kosmos.setSceneTransition(Idle(Scenes.Gone))
@@ -498,7 +564,7 @@ class SceneInteractorTest : SysuiTestCase() {

    @Test
    fun transitionValue_test_transitions() =
        testScope.runTest {
        kosmos.runTest {
            val transitionValue by collectLastValue(underTest.transitionProgress(Scenes.Gone))
            val progress = MutableStateFlow(0f)

@@ -525,7 +591,7 @@ class SceneInteractorTest : SysuiTestCase() {

    @Test
    fun changeScene_toGone_whenKeyguardDisabled_doesNotThrow() =
        testScope.runTest {
        kosmos.runTest {
            val currentScene by collectLastValue(underTest.currentScene)
            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
            kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
@@ -539,7 +605,6 @@ class SceneInteractorTest : SysuiTestCase() {
    fun showOverlay_overlayDisabled_doesNothing() =
        kosmos.runTest {
            enableDualShade()
            runCurrent()
            val currentOverlays by collectLastValue(underTest.currentOverlays)
            val disabledOverlay = Overlays.QuickSettingsShade
            fakeDisableFlagsRepository.disableFlags.value =
@@ -556,7 +621,6 @@ class SceneInteractorTest : SysuiTestCase() {
    fun replaceOverlay_withDisabledOverlay_doesNothing() =
        kosmos.runTest {
            enableDualShade()
            runCurrent()
            val currentOverlays by collectLastValue(underTest.currentOverlays)
            val showingOverlay = Overlays.NotificationsShade
            underTest.showOverlay(showingOverlay, "reason")
@@ -636,7 +700,6 @@ class SceneInteractorTest : SysuiTestCase() {
    fun changeScene_toIncorrectShade_crashes() =
        kosmos.runTest {
            enableDualShade()
            runCurrent()
            underTest.changeScene(Scenes.Shade, "reason")
        }

@@ -644,7 +707,6 @@ class SceneInteractorTest : SysuiTestCase() {
    fun changeScene_toIncorrectQuickSettings_crashes() =
        kosmos.runTest {
            enableDualShade()
            runCurrent()
            underTest.changeScene(Scenes.QuickSettings, "reason")
        }

@@ -652,15 +714,13 @@ class SceneInteractorTest : SysuiTestCase() {
    fun snapToScene_toIncorrectShade_crashes() =
        kosmos.runTest {
            enableDualShade()
            runCurrent()
            underTest.snapToScene(Scenes.Shade, "reason")
            underTest.snapToScene(Scenes.Shade, loggingReason = "reason")
        }

    @Test(expected = IllegalStateException::class)
    fun snapToScene_toIncorrectQuickSettings_crashes() =
        kosmos.runTest {
            enableDualShade()
            runCurrent()
            underTest.changeScene(Scenes.QuickSettings, "reason")
        }

@@ -668,7 +728,6 @@ class SceneInteractorTest : SysuiTestCase() {
    fun showOverlay_incorrectShadeOverlay_crashes() =
        kosmos.runTest {
            disableDualShade()
            runCurrent()
            underTest.showOverlay(Overlays.NotificationsShade, "reason")
        }

@@ -676,7 +735,6 @@ class SceneInteractorTest : SysuiTestCase() {
    fun showOverlay_incorrectQuickSettingsOverlay_crashes() =
        kosmos.runTest {
            disableDualShade()
            runCurrent()
            underTest.showOverlay(Overlays.QuickSettingsShade, "reason")
        }

@@ -684,7 +742,6 @@ class SceneInteractorTest : SysuiTestCase() {
    fun instantlyShowOverlay() =
        kosmos.runTest {
            enableDualShade()
            runCurrent()
            val currentScene by collectLastValue(underTest.currentScene)
            val currentOverlays by collectLastValue(underTest.currentOverlays)
            val originalScene = currentScene
@@ -692,7 +749,6 @@ class SceneInteractorTest : SysuiTestCase() {

            val overlay = Overlays.NotificationsShade
            underTest.instantlyShowOverlay(overlay, "reason")
            runCurrent()

            assertThat(currentScene).isEqualTo(originalScene)
            assertThat(currentOverlays).contains(overlay)
@@ -702,17 +758,14 @@ class SceneInteractorTest : SysuiTestCase() {
    fun instantlyHideOverlay() =
        kosmos.runTest {
            enableDualShade()
            runCurrent()
            val currentScene by collectLastValue(underTest.currentScene)
            val currentOverlays by collectLastValue(underTest.currentOverlays)
            val overlay = Overlays.QuickSettingsShade
            underTest.showOverlay(overlay, "reason")
            runCurrent()
            val originalScene = currentScene
            assertThat(currentOverlays).contains(overlay)

            underTest.instantlyHideOverlay(overlay, "reason")
            runCurrent()

            assertThat(currentScene).isEqualTo(originalScene)
            assertThat(currentOverlays).isEmpty()
@@ -726,9 +779,7 @@ class SceneInteractorTest : SysuiTestCase() {
            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
                SuccessFingerprintAuthenticationStatus(0, true)
            )
            runCurrent()
            underTest.changeScene(toScene = Scenes.Gone, loggingReason = "")
            runCurrent()
            assertThat(currentScene).isEqualTo(Scenes.Gone)

            val processor = mock<SceneInteractor.OnSceneAboutToChangeListener>()
@@ -739,7 +790,6 @@ class SceneInteractorTest : SysuiTestCase() {
                sceneState = KeyguardState.AOD,
                loggingReason = "",
            )
            runCurrent()
            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)

            verify(processor).onSceneAboutToChange(Scenes.Lockscreen, KeyguardState.AOD)
@@ -808,7 +858,7 @@ class SceneInteractorTest : SysuiTestCase() {
                SuccessFingerprintAuthenticationStatus(0, true)
            )

            underTest.snapToScene(Scenes.Lockscreen, "reason")
            underTest.snapToScene(Scenes.Lockscreen, loggingReason = "reason")

            assertThat(topmostContent).isEqualTo(Scenes.Lockscreen)

@@ -821,7 +871,6 @@ class SceneInteractorTest : SysuiTestCase() {
    fun topmostContent_sceneChange_withOverlay() =
        kosmos.runTest {
            kosmos.enableDualShade()
            runCurrent()

            val topmostContent by collectLastValue(underTest.topmostContent)

@@ -829,12 +878,12 @@ class SceneInteractorTest : SysuiTestCase() {
                SuccessFingerprintAuthenticationStatus(0, true)
            )

            underTest.snapToScene(Scenes.Lockscreen, "reason")
            underTest.snapToScene(Scenes.Lockscreen, loggingReason = "reason")
            underTest.showOverlay(Overlays.NotificationsShade, "reason")

            assertThat(topmostContent).isEqualTo(Overlays.NotificationsShade)

            underTest.changeScene(Scenes.Gone, "reason")
            underTest.changeScene(Scenes.Gone, loggingReason = "reason", hideAllOverlays = false)

            assertThat(topmostContent).isEqualTo(Overlays.NotificationsShade)
        }
@@ -842,8 +891,7 @@ class SceneInteractorTest : SysuiTestCase() {
    @Test
    fun topmostContent_overlayChange_higherZOrder() =
        kosmos.runTest {
            kosmos.enableDualShade()
            runCurrent()
            enableDualShade()

            val topmostContent by collectLastValue(underTest.topmostContent)

@@ -851,7 +899,7 @@ class SceneInteractorTest : SysuiTestCase() {
                SuccessFingerprintAuthenticationStatus(0, true)
            )

            underTest.snapToScene(Scenes.Lockscreen, "reason")
            underTest.snapToScene(Scenes.Lockscreen, loggingReason = "reason")
            underTest.showOverlay(Overlays.NotificationsShade, "reason")

            assertThat(topmostContent).isEqualTo(Overlays.NotificationsShade)
@@ -864,8 +912,7 @@ class SceneInteractorTest : SysuiTestCase() {
    @Test
    fun topmostContent_overlayChange_lowerZOrder() =
        kosmos.runTest {
            kosmos.enableDualShade()
            runCurrent()
            enableDualShade()

            val topmostContent by collectLastValue(underTest.topmostContent)

@@ -873,7 +920,7 @@ class SceneInteractorTest : SysuiTestCase() {
                SuccessFingerprintAuthenticationStatus(0, true)
            )

            underTest.snapToScene(Scenes.Lockscreen, "reason")
            underTest.snapToScene(Scenes.Lockscreen, loggingReason = "reason")
            underTest.showOverlay(Overlays.QuickSettingsShade, "reason")

            assertThat(topmostContent).isEqualTo(Overlays.QuickSettingsShade)
+22 −20
Original line number Diff line number Diff line
@@ -250,8 +250,8 @@ constructor(
     * scene, any current transition will be canceled and an animation to the target scene will be
     * started.
     *
     * If [Overlays.Bouncer] is showing, we trigger an instant scene change as it will not be user-
     * visible, and trigger a transition to hide the bouncer.
     * If [hideAllOverlays] is `true`, any visible overlays will be hidden (with transition), even
     * if the scene change is rejected.
     */
    @JvmOverloads
    fun changeScene(
@@ -260,10 +260,10 @@ constructor(
        transitionKey: TransitionKey? = null,
        sceneState: Any? = null,
        forceSettleToTargetScene: Boolean = false,
        hideAllOverlays: Boolean = true,
    ) {
        val currentSceneKey = currentScene.value
        val resolvedScene = sceneFamilyResolvers.get()[toScene]?.resolvedScene?.value ?: toScene
        val bouncerShowing = Overlays.Bouncer in currentOverlays.value

        if (resolvedScene == currentSceneKey && forceSettleToTargetScene) {
            logger.logSceneChangeCancellation(scene = resolvedScene, sceneState = sceneState)
@@ -273,6 +273,12 @@ constructor(
            repository.freezeAndAnimateToCurrentState()
        }

        if (hideAllOverlays) {
            currentOverlays.value.forEach {
                hideOverlay(it, "Hiding overlay ${it.debugName} due to scene change request")
            }
        }

        if (
            !validateSceneChange(
                from = currentSceneKey,
@@ -280,12 +286,6 @@ constructor(
                loggingReason = loggingReason,
            )
        ) {
            if (bouncerShowing) {
                hideOverlay(
                    Overlays.Bouncer,
                    "Scene change cancelled but hiding bouncer for: ($loggingReason)",
                )
            }
            return
        }

@@ -299,22 +299,19 @@ constructor(
            isInstant = false,
        )

        if (bouncerShowing) {
            repository.instantlyTransitionTo(resolvedScene)
            hideOverlay(Overlays.Bouncer, "Hiding on changeScene for: ($loggingReason)")
        } else {
        repository.changeScene(resolvedScene, transitionKey)
    }
    }

    /**
     * Requests a scene change to the given scene.
     *
     * The change is instantaneous and not animated; it will be observable in the next frame and
     * there will be no transition animation. If [Overlays.Bouncer] is showing, it will instantly be
     * hidden.
     * there will be no transition animation.
     *
     * If [hideAllOverlays] is `true`, any visible overlays will be instantly hidden, even if the
     * scene change is rejected.
     */
    fun snapToScene(toScene: SceneKey, loggingReason: String) {
    fun snapToScene(toScene: SceneKey, loggingReason: String, hideAllOverlays: Boolean = true) {
        val currentSceneKey = currentScene.value
        val resolvedScene =
            sceneFamilyResolvers.get()[toScene]?.let { familyResolver ->
@@ -331,6 +328,9 @@ constructor(
                loggingReason = loggingReason,
            )
        ) {
            if (hideAllOverlays) {
                repository.instantlyTransitionTo(overlays = emptySet())
            }
            return
        }

@@ -342,8 +342,10 @@ constructor(
            isInstant = true,
        )

        repository.instantlyTransitionTo(resolvedScene)
        instantlyHideOverlay(Overlays.Bouncer, "Hiding on snapToScene for: ($loggingReason)")
        repository.instantlyTransitionTo(
            scene = resolvedScene,
            overlays = if (hideAllOverlays) emptySet() else currentOverlays.value,
        )
    }

    /**