summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt206
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt30
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt107
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt25
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt13
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorKosmos.kt29
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerStartableKosmos.kt6
22 files changed, 483 insertions, 134 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index b83c0ce702a7..ecfcc90982c0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -38,6 +38,7 @@ 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.scene.domain.interactor.sceneContainerStartable
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.testKosmos
@@ -67,10 +68,13 @@ class BouncerViewModelTest : SysuiTestCase() {
private val testScope = kosmos.testScope
private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
+ private val sceneContainerStartable = kosmos.sceneContainerStartable
+
private lateinit var underTest: BouncerViewModel
@Before
fun setUp() {
+ sceneContainerStartable.start()
underTest = kosmos.bouncerViewModel
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 719828ce4a0a..d2a458c8a055 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -29,6 +29,7 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
@@ -38,6 +39,8 @@ 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.res.R
+import com.android.systemui.scene.domain.interactor.sceneBackInteractor
+import com.android.systemui.scene.domain.interactor.sceneContainerStartable
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
@@ -58,6 +61,7 @@ import org.mockito.Mockito.verify
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
class QuickSettingsSceneViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
@@ -71,6 +75,8 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
private val footerActionsController = mock<FooterActionsController>()
private val sceneInteractor = kosmos.sceneInteractor
+ private val sceneBackInteractor = kosmos.sceneBackInteractor
+ private val sceneContainerStartable = kosmos.sceneContainerStartable
private lateinit var underTest: QuickSettingsSceneViewModel
@@ -79,6 +85,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
fun setUp() {
kosmos.fakeFeatureFlagsClassic.set(Flags.NEW_NETWORK_SLICE_UI, false)
+ sceneContainerStartable.start()
underTest =
QuickSettingsSceneViewModel(
applicationScope = testScope.backgroundScope,
@@ -89,7 +96,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
notifications = kosmos.notificationsPlaceholderViewModel,
footerActionsViewModelFactory = footerActionsViewModelFactory,
footerActionsController = footerActionsController,
- sceneInteractor = sceneInteractor,
+ sceneBackInteractor = sceneBackInteractor,
)
}
@@ -127,11 +134,12 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
val destinations by collectLastValue(underTest.destinationScenes)
val currentScene by collectLastValue(sceneInteractor.currentScene)
- val previousScene by collectLastValue(sceneInteractor.previousScene())
+ val backScene by collectLastValue(sceneBackInteractor.backScene)
sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
sceneInteractor.changeScene(Scenes.QuickSettings, "reason")
assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
- assertThat(previousScene).isEqualTo(Scenes.Lockscreen)
+ assertThat(backScene).isEqualTo(Scenes.Lockscreen)
+
assertThat(destinations)
.isEqualTo(
mapOf(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
index 8e2eea178708..a45ac9f7d95d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
@@ -140,23 +140,4 @@ class SceneContainerRepositoryTest : SysuiTestCase() {
ObservableTransitionState.Idle(kosmos.sceneContainerConfig.initialSceneKey)
)
}
-
- @Test
- fun previousScene() =
- testScope.runTest {
- val underTest = kosmos.sceneContainerRepository
- val currentScene by collectLastValue(underTest.currentScene)
- val previousScene by collectLastValue(underTest.previousScene)
-
- assertThat(previousScene).isNull()
-
- val firstScene = currentScene
- underTest.changeScene(Scenes.Shade)
- assertThat(previousScene).isEqualTo(firstScene)
- assertThat(currentScene).isEqualTo(Scenes.Shade)
-
- underTest.changeScene(Scenes.QuickSettings)
- assertThat(previousScene).isEqualTo(Scenes.Shade)
- assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt
new file mode 100644
index 000000000000..c75e297f23c8
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt
@@ -0,0 +1,206 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.scene.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.AuthenticationResult
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SceneBackInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val sceneInteractor = kosmos.sceneInteractor
+ private val sceneContainerStartable = kosmos.sceneContainerStartable
+ private val authenticationInteractor = kosmos.authenticationInteractor
+
+ private val underTest = kosmos.sceneBackInteractor
+
+ @Test
+ @EnableSceneContainer
+ fun navigateToQs_thenBouncer_thenBack_whileLocked() =
+ testScope.runTest {
+ sceneContainerStartable.start()
+
+ assertRoute(
+ RouteNode(Scenes.Lockscreen, null),
+ RouteNode(Scenes.Shade, Scenes.Lockscreen),
+ RouteNode(Scenes.QuickSettings, Scenes.Shade),
+ RouteNode(Scenes.Bouncer, Scenes.QuickSettings),
+ RouteNode(Scenes.QuickSettings, Scenes.Shade),
+ RouteNode(Scenes.Shade, Scenes.Lockscreen),
+ RouteNode(Scenes.Lockscreen, null),
+ )
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun navigateToQs_thenBouncer_thenUnlock() =
+ testScope.runTest {
+ sceneContainerStartable.start()
+
+ assertRoute(
+ RouteNode(Scenes.Lockscreen, null),
+ RouteNode(Scenes.Shade, Scenes.Lockscreen),
+ RouteNode(Scenes.QuickSettings, Scenes.Shade),
+ RouteNode(Scenes.Bouncer, Scenes.QuickSettings, unlockDevice = true),
+ RouteNode(Scenes.Gone, null),
+ )
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun navigateToQs_skippingShade_thenBouncer_thenBack_whileLocked() =
+ testScope.runTest {
+ sceneContainerStartable.start()
+
+ assertRoute(
+ RouteNode(Scenes.Lockscreen, null),
+ RouteNode(Scenes.QuickSettings, Scenes.Lockscreen),
+ RouteNode(Scenes.Bouncer, Scenes.QuickSettings),
+ RouteNode(Scenes.QuickSettings, Scenes.Lockscreen),
+ RouteNode(Scenes.Lockscreen, null),
+ )
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun navigateToBouncer_thenBack_whileLocked() =
+ testScope.runTest {
+ sceneContainerStartable.start()
+
+ assertRoute(
+ RouteNode(Scenes.Lockscreen, null),
+ RouteNode(Scenes.Bouncer, Scenes.Lockscreen),
+ RouteNode(Scenes.Lockscreen, null),
+ )
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun navigateToQs_skippingShade_thenBouncer_thenBack_thenShade_whileLocked() =
+ testScope.runTest {
+ sceneContainerStartable.start()
+
+ assertRoute(
+ RouteNode(Scenes.Lockscreen, null),
+ RouteNode(Scenes.QuickSettings, Scenes.Lockscreen),
+ RouteNode(Scenes.Bouncer, Scenes.QuickSettings),
+ RouteNode(Scenes.QuickSettings, Scenes.Lockscreen),
+ RouteNode(Scenes.Lockscreen, null),
+ RouteNode(Scenes.Shade, Scenes.Lockscreen),
+ )
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun navigateToQs_thenBack_whileUnlocked() =
+ testScope.runTest {
+ sceneContainerStartable.start()
+ unlockDevice()
+
+ assertRoute(
+ RouteNode(Scenes.Gone, null),
+ RouteNode(Scenes.Shade, Scenes.Gone),
+ RouteNode(Scenes.QuickSettings, Scenes.Shade),
+ RouteNode(Scenes.Shade, Scenes.Gone),
+ RouteNode(Scenes.Gone, null),
+ )
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun navigateToQs_skippingShade_thenBack_whileUnlocked() =
+ testScope.runTest {
+ sceneContainerStartable.start()
+ unlockDevice()
+
+ assertRoute(
+ RouteNode(Scenes.Gone, null),
+ RouteNode(Scenes.QuickSettings, Scenes.Gone),
+ RouteNode(Scenes.Gone, null),
+ )
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun navigateToQs_skippingShade_thenBack_thenShade_whileUnlocked() =
+ testScope.runTest {
+ sceneContainerStartable.start()
+ unlockDevice()
+
+ assertRoute(
+ RouteNode(Scenes.Gone, null),
+ RouteNode(Scenes.QuickSettings, Scenes.Gone),
+ RouteNode(Scenes.Gone, null),
+ RouteNode(Scenes.Shade, Scenes.Gone),
+ )
+ }
+
+ private suspend fun TestScope.assertRoute(vararg route: RouteNode) {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val backScene by collectLastValue(underTest.backScene)
+
+ route.forEachIndexed { index, node ->
+ sceneInteractor.changeScene(node.changeSceneTo, "")
+ assertWithMessage("node at index $index currentScene mismatch")
+ .that(currentScene)
+ .isEqualTo(node.changeSceneTo)
+ assertWithMessage("node at index $index backScene mismatch")
+ .that(backScene)
+ .isEqualTo(node.expectedBackScene)
+ if (node.unlockDevice) {
+ unlockDevice()
+ }
+ }
+ }
+
+ private suspend fun TestScope.unlockDevice() {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ runCurrent()
+ assertThat(authenticationInteractor.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
+ .isEqualTo(AuthenticationResult.SUCCEEDED)
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+ }
+
+ private data class RouteNode(
+ val changeSceneTo: SceneKey,
+ val expectedBackScene: SceneKey? = null,
+ val unlockDevice: Boolean = false,
+ )
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 871ce6d56e97..2fb8212e2b7c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -291,34 +291,4 @@ class SceneInteractorTest : SysuiTestCase() {
assertThat(isVisible).isFalse()
}
-
- @Test
- fun previousScene() =
- testScope.runTest {
- val currentScene by collectLastValue(underTest.currentScene)
- val previousScene by collectLastValue(underTest.previousScene())
- assertThat(previousScene).isNull()
-
- val firstScene = currentScene
- underTest.changeScene(toScene = Scenes.Shade, "reason")
- assertThat(previousScene).isEqualTo(firstScene)
-
- underTest.changeScene(toScene = Scenes.QuickSettings, "reason")
- assertThat(previousScene).isEqualTo(Scenes.Shade)
- }
-
- @Test
- fun previousScene_withIgnoredScene() =
- testScope.runTest {
- val currentScene by collectLastValue(underTest.currentScene)
- val previousScene by collectLastValue(underTest.previousScene(ignored = Scenes.Shade))
- assertThat(previousScene).isNull()
-
- val firstScene = currentScene
- underTest.changeScene(toScene = Scenes.Shade, "reason")
- assertThat(previousScene).isEqualTo(firstScene)
-
- underTest.changeScene(toScene = Scenes.QuickSettings, "reason")
- assertThat(previousScene).isNull()
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 2586ad54156f..5779e37cb1ed 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -392,6 +392,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
Scenes.Gone,
Scenes.Lockscreen,
Scenes.Bouncer,
+ Scenes.Gone,
Scenes.Shade,
Scenes.QuickSettings,
)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index cb458efbdf69..45e39caf6cff 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -34,7 +34,7 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.log.SessionTracker
import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.domain.interactor.SceneBackInteractor
import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -59,7 +59,7 @@ constructor(
private val powerInteractor: PowerInteractor,
private val uiEventLogger: UiEventLogger,
private val sessionTracker: SessionTracker,
- sceneInteractor: SceneInteractor,
+ sceneBackInteractor: SceneBackInteractor,
) {
private val _onIncorrectBouncerInput = MutableSharedFlow<Unit>()
val onIncorrectBouncerInput: SharedFlow<Unit> = _onIncorrectBouncerInput
@@ -95,7 +95,9 @@ constructor(
/** The scene to show when bouncer is dismissed. */
val dismissDestination: Flow<SceneKey> =
- sceneInteractor.previousScene(Scenes.Bouncer).map { it ?: Scenes.Lockscreen }
+ sceneBackInteractor.backScene
+ .filter { it != Scenes.Bouncer }
+ .map { it ?: Scenes.Lockscreen }
/** Notifies that the user has places down a pointer, not necessarily dragging just yet. */
fun onDown() {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index 7fa091ac6455..2406cc6bcea2 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -76,7 +76,12 @@ interface CommunalModule {
val config =
SceneContainerConfig(
sceneKeys = listOf(CommunalScenes.Blank, CommunalScenes.Communal),
- initialSceneKey = CommunalScenes.Blank
+ initialSceneKey = CommunalScenes.Blank,
+ navigationDistances =
+ mapOf(
+ CommunalScenes.Blank to 0,
+ CommunalScenes.Communal to 1,
+ ),
)
return SceneDataSourceDelegator(applicationScope, config)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
index c1986fa93dd9..22146ce3a18f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.qs.ui.viewmodel
import androidx.lifecycle.LifecycleOwner
@@ -30,7 +32,7 @@ import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
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.domain.interactor.SceneBackInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
@@ -42,6 +44,8 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/** Models UI state and handles user input for the quick settings scene. */
@@ -57,21 +61,30 @@ constructor(
val notifications: NotificationsPlaceholderViewModel,
private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
private val footerActionsController: FooterActionsController,
- sceneInteractor: SceneInteractor,
+ sceneBackInteractor: SceneBackInteractor,
) {
- @OptIn(ExperimentalCoroutinesApi::class)
+ private val backScene: StateFlow<SceneKey> =
+ sceneBackInteractor.backScene
+ .filter { it != Scenes.QuickSettings }
+ .map { it ?: Scenes.Shade }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = Scenes.Shade,
+ )
+
val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
combine(
deviceEntryInteractor.isUnlocked,
deviceEntryInteractor.canSwipeToEnter,
qsSceneAdapter.isCustomizing,
- sceneInteractor.previousScene(ignored = Scenes.QuickSettings),
- ) { isUnlocked, canSwipeToDismiss, isCustomizing, previousScene ->
+ backScene,
+ ) { isUnlocked, canSwipeToDismiss, isCustomizing, backScene ->
destinationScenes(
isUnlocked,
canSwipeToDismiss,
isCustomizing,
- previousScene,
+ backScene,
)
}
.stateIn(
@@ -82,8 +95,7 @@ constructor(
isUnlocked = deviceEntryInteractor.isUnlocked.value,
canSwipeToDismiss = deviceEntryInteractor.canSwipeToEnter.value,
isCustomizing = qsSceneAdapter.isCustomizing.value,
- previousScene = sceneInteractor
- .previousScene(ignored = Scenes.QuickSettings).value,
+ backScene = backScene.value,
),
)
@@ -91,7 +103,7 @@ constructor(
isUnlocked: Boolean,
canSwipeToDismiss: Boolean?,
isCustomizing: Boolean,
- previousScene: SceneKey?
+ backScene: SceneKey?,
): Map<UserAction, UserActionResult> {
val upBottomEdge =
when {
@@ -108,13 +120,15 @@ constructor(
// TODO(b/330200163) Add an Up from Bottom to be able to collapse the shade
// while customizing
} else {
- this[Back] = UserActionResult(previousScene ?: Scenes.Shade)
- this[Swipe(SwipeDirection.Up)] = UserActionResult(previousScene ?: Scenes.Shade)
- this[
+ put(Back, UserActionResult(backScene ?: Scenes.Shade))
+ put(Swipe(SwipeDirection.Up), UserActionResult(backScene ?: Scenes.Shade))
+ put(
Swipe(
fromSource = Edge.Bottom,
direction = SwipeDirection.Up,
- )] = UserActionResult(upBottomEdge)
+ ),
+ UserActionResult(upBottomEdge),
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
index 8277c73025d8..2a73b5394a79 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
@@ -47,6 +47,12 @@ object KeyguardlessSceneContainerFrameworkModule {
Scenes.Shade,
),
initialSceneKey = Scenes.Gone,
+ navigationDistances =
+ mapOf(
+ Scenes.Gone to 0,
+ Scenes.Shade to 1,
+ Scenes.QuickSettings to 2,
+ ),
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
index 69f9443b334b..cd1b96508cce 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -73,6 +73,15 @@ interface SceneContainerFrameworkModule {
Scenes.Shade,
),
initialSceneKey = Scenes.Lockscreen,
+ navigationDistances =
+ mapOf(
+ Scenes.Gone to 0,
+ Scenes.Lockscreen to 0,
+ Scenes.Communal to 1,
+ Scenes.Shade to 2,
+ Scenes.QuickSettings to 3,
+ Scenes.Bouncer to 4,
+ ),
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt
index d202c24ae152..b918277bd3a4 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt
@@ -47,6 +47,11 @@ object ShadelessSceneContainerFrameworkModule {
Scenes.Bouncer,
),
initialSceneKey = Scenes.Lockscreen,
+ mapOf(
+ Scenes.Gone to 0,
+ Scenes.Lockscreen to 0,
+ Scenes.Bouncer to 1,
+ )
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
index 3082eb904a51..994b01216c22 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
@@ -24,8 +24,6 @@ import com.android.compose.animation.scene.TransitionKey
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneDataSource
-import com.android.systemui.util.kotlin.WithPrev
-import com.android.systemui.util.kotlin.pairwise
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -36,7 +34,6 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/** Source of truth for scene framework application state. */
@@ -47,32 +44,7 @@ constructor(
private val config: SceneContainerConfig,
private val dataSource: SceneDataSource,
) {
- private val previousAndCurrentScene: StateFlow<WithPrev<SceneKey?, SceneKey>> =
- dataSource.currentScene
- .pairwise()
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = WithPrev(null, dataSource.currentScene.value),
- )
-
- val currentScene: StateFlow<SceneKey> =
- previousAndCurrentScene
- .map { it.newValue }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = previousAndCurrentScene.value.newValue,
- )
-
- val previousScene: StateFlow<SceneKey?> =
- previousAndCurrentScene
- .map { it.previousValue }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = previousAndCurrentScene.value.previousValue,
- )
+ val currentScene: StateFlow<SceneKey> = dataSource.currentScene
private val _isVisible = MutableStateFlow(true)
val isVisible: StateFlow<Boolean> = _isVisible.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt
new file mode 100644
index 000000000000..f66d08f781f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt
@@ -0,0 +1,107 @@
+/*
+ * 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.scene.domain.interactor
+
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.scene.shared.logger.SceneLogger
+import com.android.systemui.scene.shared.model.SceneContainerConfig
+import java.util.Stack
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+@SysUISingleton
+class SceneBackInteractor
+@Inject
+constructor(
+ private val logger: SceneLogger,
+ private val sceneContainerConfig: SceneContainerConfig,
+) {
+ private val _backScene = MutableStateFlow<SceneKey?>(null)
+ /**
+ * The scene to navigate to when the user triggers back navigation.
+ *
+ * This is meant for scene implementations to consult with when they implement their destination
+ * scene flow.
+ *
+ * Note that this flow could emit any scene from the [SceneContainerConfig] and that it's an
+ * illegal state to have scene implementation map to itself in its destination scene flow. Thus,
+ * scene implementations might wish to filter their own scene key out before using this.
+ */
+ val backScene: StateFlow<SceneKey?> = _backScene.asStateFlow()
+
+ private val backStack = Stack<SceneKey>()
+
+ fun onSceneChange(from: SceneKey, to: SceneKey) {
+ check(from != to) { "from == to, from=${from.debugName}, to=${to.debugName}" }
+ when (stackOperation(from, to)) {
+ Clear -> {
+ backStack.clear()
+ }
+ Push -> {
+ backStack.push(from)
+ }
+ Pop -> {
+ check(backStack.isNotEmpty()) { "Cannot pop ${from.debugName} when stack is empty" }
+ val popped = backStack.pop()
+ check(to == popped) {
+ "Expected to pop ${to.debugName} but instead popped ${popped.debugName}"
+ }
+ }
+ }
+
+ logger.logSceneBackStack(backStack)
+ _backScene.value = peek()
+ }
+
+ private fun stackOperation(from: SceneKey, to: SceneKey): StackOperation {
+ val fromDistance =
+ checkNotNull(sceneContainerConfig.navigationDistances[from]) {
+ "No distance mapping for scene \"${from.debugName}\"!"
+ }
+ val toDistance =
+ checkNotNull(sceneContainerConfig.navigationDistances[to]) {
+ "No distance mapping for scene \"${to.debugName}\"!"
+ }
+
+ return when {
+ toDistance == 0 -> Clear
+ toDistance > fromDistance -> Push
+ toDistance < fromDistance -> Pop
+ else ->
+ error(
+ "No mapping when from=${from.debugName} (distance=$fromDistance)," +
+ " to=${to.debugName} (distance=$toDistance)!"
+ )
+ }
+ }
+
+ private fun peek(): SceneKey? {
+ return if (backStack.isNotEmpty()) {
+ backStack.peek()
+ } else {
+ null
+ }
+ }
+
+ private sealed interface StackOperation
+ private data object Clear : StackOperation
+ private data object Push : StackOperation
+ private data object Pop : StackOperation
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 8ced22223527..2ccd3b9e9f8a 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -140,33 +140,6 @@ constructor(
)
/**
- * The previous scene (or `null` if the previous scene is the [ignored] scene).
- *
- * This is effectively the previous value of [currentScene] which means that all caveats, for
- * example regarding when in a transition the current scene changes, apply.
- *
- * @param ignored If the previous scene is the same as [ignored], `null` is emitted. This is
- * designed to reduce the chances of a scene using [previousScene] naively to then set up a
- * user action that ends up leading to itself, which is an illegal operation that would cause
- * a crash.
- */
- fun previousScene(
- ignored: SceneKey? = null,
- ): StateFlow<SceneKey?> {
- fun SceneKey?.nullifyIfIgnored(): SceneKey? {
- return this?.takeIf { this != ignored }
- }
-
- return repository.previousScene
- .map { it.nullifyIfIgnored() }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = repository.previousScene.value.nullifyIfIgnored(),
- )
- }
-
- /**
* Returns the keys of all scenes in the container.
*
* The scenes will be sorted in z-order such that the last one is the one that should be
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 4fc24b898605..39ec12f2701c 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -45,6 +45,7 @@ import com.android.systemui.model.updateFlags
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.FalsingManager.FalsingBeliefListener
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.scene.domain.interactor.SceneBackInteractor
import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -56,6 +57,7 @@ import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNoti
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
import com.android.systemui.util.asIndenting
+import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.printSection
import com.android.systemui.util.println
@@ -111,6 +113,7 @@ constructor(
private val faceUnlockInteractor: DeviceEntryFaceAuthInteractor,
private val shadeInteractor: ShadeInteractor,
private val uiEventLogger: UiEventLogger,
+ private val sceneBackInteractor: SceneBackInteractor,
) : CoreStartable {
override fun start() {
@@ -124,6 +127,7 @@ constructor(
hydrateInteractionState()
handleBouncerOverscroll()
hydrateWindowController()
+ hydrateBackStack()
} else {
sceneLogger.logFrameworkEnabled(
isEnabled = false,
@@ -257,8 +261,7 @@ constructor(
// Track the previous scene (sans Bouncer), so that we know where to go when the device
// is unlocked whilst on the bouncer.
val previousScene =
- sceneInteractor
- .previousScene()
+ sceneBackInteractor.backScene
.filterNot { it == Scenes.Bouncer }
.stateIn(this, SharingStarted.Eagerly, initialValue = null)
deviceUnlockedInteractor.deviceUnlockStatus
@@ -581,4 +584,12 @@ constructor(
loggingReason = loggingReason,
)
}
+
+ private fun hydrateBackStack() {
+ applicationScope.launch {
+ sceneInteractor.currentScene.pairwise().collect { (from, to) ->
+ sceneBackInteractor.onSceneChange(from = from, to = to)
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
index f44779ade8db..5ebdd8698656 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
@@ -20,6 +20,7 @@ import com.android.compose.animation.scene.SceneKey
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
import com.android.systemui.log.dagger.SceneFrameworkLog
+import java.util.Stack
import javax.inject.Inject
class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer: LogBuffer) {
@@ -102,7 +103,7 @@ class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer:
tag = TAG,
level = LogLevel.INFO,
messageInitializer = { str1 = reason },
- messagePrinter = { "remote user interaction started, reason: $str3" },
+ messagePrinter = { "remote user interaction started, reason: $str1" },
)
}
@@ -115,6 +116,15 @@ class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer:
)
}
+ fun logSceneBackStack(backStack: Stack<SceneKey>) {
+ logBuffer.log(
+ tag = TAG,
+ level = LogLevel.INFO,
+ messageInitializer = { str1 = backStack.joinToString(", ") { it.debugName } },
+ messagePrinter = { "back stack: $str1" },
+ )
+ }
+
companion object {
private const val TAG = "SceneFramework"
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt
index 53cdaaab7478..0a30c31ca739 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt
@@ -25,6 +25,9 @@ data class SceneContainerConfig(
* The keys to all scenes in the container, sorted by z-order such that the last one renders on
* top of all previous ones. Scene keys within the same container must not repeat but it's okay
* to have the same scene keys in different containers.
+ *
+ * Note that this doesn't control how back navigation works; for that, we have
+ * [navigationDistances].
*/
val sceneKeys: List<SceneKey>,
@@ -33,6 +36,24 @@ data class SceneContainerConfig(
* before taking any application state in to account.
*/
val initialSceneKey: SceneKey,
+
+ /**
+ * Navigation distance of each scene.
+ *
+ * The navigation distance is a measure of how many non-back user action "steps" away from the
+ * starting scene, each scene is.
+ *
+ * The framework uses these to help scene implementations decide which scene to go back to when
+ * the user attempts to navigate back on them, if they need that.
+ *
+ * In general, the more non-back user actions are needed to get to a scene, the greater that
+ * scene's distance should be. Navigating "back" then goes from scenes with a higher distance to
+ * scenes with a lower distance.
+ *
+ * Note that this is not the z-order of rendering; that's determined by the order of declaration
+ * of scenes in the [sceneKeys] list.
+ */
+ val navigationDistances: Map<SceneKey, Int>
) {
init {
check(sceneKeys.isNotEmpty()) { "A container must have at least one scene key." }
@@ -40,5 +61,9 @@ data class SceneContainerConfig(
check(sceneKeys.contains(initialSceneKey)) {
"The initial key \"$initialSceneKey\" is not present in this container."
}
+
+ check(navigationDistances.keys == sceneKeys.toSet()) {
+ "Scene keys and distance map must match."
+ }
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
index 4a02f6ddbebd..43948472597d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
@@ -26,7 +26,7 @@ import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.sessionTracker
import com.android.systemui.power.domain.interactor.powerInteractor
-import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.domain.interactor.sceneBackInteractor
val Kosmos.bouncerInteractor by Fixture {
BouncerInteractor(
@@ -38,6 +38,6 @@ val Kosmos.bouncerInteractor by Fixture {
powerInteractor = powerInteractor,
uiEventLogger = uiEventLogger,
sessionTracker = sessionTracker,
- sceneInteractor = sceneInteractor,
+ sceneBackInteractor = sceneBackInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
index 7f6a7bd3f7d8..16d08dd91ca8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
@@ -32,4 +32,15 @@ val Kosmos.fakeScenes by Fixture {
val Kosmos.scenes by Fixture { fakeScenes }
val Kosmos.initialSceneKey by Fixture { Scenes.Lockscreen }
-val Kosmos.sceneContainerConfig by Fixture { SceneContainerConfig(sceneKeys, initialSceneKey) }
+val Kosmos.sceneContainerConfig by Fixture {
+ val navigationDistances =
+ mapOf(
+ Scenes.Gone to 0,
+ Scenes.Lockscreen to 0,
+ Scenes.Communal to 1,
+ Scenes.Shade to 2,
+ Scenes.QuickSettings to 3,
+ Scenes.Bouncer to 4,
+ )
+ SceneContainerConfig(sceneKeys, initialSceneKey, navigationDistances)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorKosmos.kt
new file mode 100644
index 000000000000..e46ede65bfb6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.scene.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.scene.sceneContainerConfig
+import com.android.systemui.scene.shared.logger.sceneLogger
+
+val Kosmos.sceneBackInteractor by Fixture {
+ SceneBackInteractor(
+ logger = sceneLogger,
+ sceneContainerConfig = sceneContainerConfig,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerStartableKosmos.kt
index c7cf9342f87e..c0f50393b1d7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerStartableKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerStartableKosmos.kt
@@ -39,7 +39,6 @@ import com.android.systemui.statusbar.notification.stack.domain.interactor.heads
import com.android.systemui.statusbar.notificationShadeWindowController
import com.android.systemui.statusbar.phone.centralSurfaces
import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
-import dagger.Lazy
val Kosmos.sceneContainerStartable by Fixture {
SceneContainerStartable(
@@ -55,8 +54,8 @@ val Kosmos.sceneContainerStartable by Fixture {
falsingCollector = falsingCollector,
falsingManager = falsingManager,
powerInteractor = powerInteractor,
- simBouncerInteractor = Lazy { simBouncerInteractor },
- authenticationInteractor = Lazy { authenticationInteractor },
+ simBouncerInteractor = { simBouncerInteractor },
+ authenticationInteractor = { authenticationInteractor },
windowController = notificationShadeWindowController,
deviceProvisioningInteractor = deviceProvisioningInteractor,
centralSurfaces = centralSurfaces,
@@ -65,5 +64,6 @@ val Kosmos.sceneContainerStartable by Fixture {
faceUnlockInteractor = deviceEntryFaceAuthInteractor,
shadeInteractor = shadeInteractor,
uiEventLogger = uiEventLogger,
+ sceneBackInteractor = sceneBackInteractor,
)
}