Loading packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +1 −0 Original line number Diff line number Diff line Loading @@ -261,6 +261,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { shadeInteractor = kosmos.shadeInteractor, footerActionsController = kosmos.footerActionsController, footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory, sceneInteractor = sceneInteractor, ) kosmos.fakeDeviceEntryRepository.setUnlocked(false) Loading packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +44 −2 Original line number Diff line number Diff line Loading @@ -42,7 +42,7 @@ import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepo import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.kosmos.testScope import com.android.systemui.model.SysUiState import com.android.systemui.model.sysUiState import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest import com.android.systemui.power.domain.interactor.PowerInteractorFactory Loading @@ -51,6 +51,7 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource import com.android.systemui.shared.system.QuickStepContract import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository Loading Loading @@ -78,6 +79,7 @@ import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.never import org.mockito.Mockito.spy import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations Loading @@ -99,7 +101,7 @@ class SceneContainerStartableTest : SysuiTestCase() { private val faceAuthRepository by lazy { kosmos.fakeDeviceEntryFaceAuthRepository } private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor } private val keyguardInteractor by lazy { kosmos.keyguardInteractor } private val sysUiState: SysUiState = mock() private val sysUiState = spy(kosmos.sysUiState) private val falsingCollector: FalsingCollector = mock() private val powerInteractor = PowerInteractorFactory.create().powerInteractor private val fakeSceneDataSource = kosmos.fakeSceneDataSource Loading Loading @@ -397,6 +399,46 @@ class SceneContainerStartableTest : SysuiTestCase() { } } @Test fun hydrateSystemUiState_onLockscreen_basedOnOcclusion() = testScope.runTest { prepareState( initialSceneKey = Scenes.Lockscreen, ) underTest.start() runCurrent() clearInvocations(sysUiState) kosmos.keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop( true, mock() ) runCurrent() assertThat( sysUiState.flags and QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED != 0 ) .isTrue() assertThat( sysUiState.flags and QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING != 0 ) .isFalse() kosmos.keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(false) runCurrent() assertThat( sysUiState.flags and QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED != 0 ) .isFalse() assertThat( sysUiState.flags and QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING != 0 ) .isTrue() } @Test fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNone() = testScope.runTest { Loading packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt +3 −17 Original line number Diff line number Diff line Loading @@ -129,6 +129,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { shadeInteractor = kosmos.shadeInteractor, footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory, footerActionsController = kosmos.footerActionsController, sceneInteractor = kosmos.sceneInteractor, ) } Loading Loading @@ -215,22 +216,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { } @Test fun onContentClicked_deviceUnlocked_switchesToGone() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene) kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pin ) kosmos.fakeDeviceEntryRepository.setUnlocked(true) runCurrent() underTest.onContentClicked() assertThat(currentScene).isEqualTo(Scenes.Gone) } @Test fun onContentClicked_deviceLockedSecurely_switchesToBouncer() = fun onContentClicked_deviceLockedSecurely_switchesToLockscreen() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene) kosmos.fakeAuthenticationRepository.setAuthenticationMethod( Loading @@ -241,7 +227,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { underTest.onContentClicked() assertThat(currentScene).isEqualTo(Scenes.Bouncer) assertThat(currentScene).isEqualTo(Scenes.Lockscreen) } @Test Loading packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt +33 −9 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.systemui.model import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.systemui.dagger.SysUISingleton import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes Loading @@ -27,6 +28,7 @@ import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICA import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED import dagger.Lazy import javax.inject.Inject Loading @@ -38,8 +40,10 @@ import javax.inject.Inject class SceneContainerPlugin @Inject constructor( private val interactor: Lazy<SceneInteractor>, private val sceneInteractor: Lazy<SceneInteractor>, private val occlusionInteractor: Lazy<SceneContainerOcclusionInteractor>, ) { /** * Returns an override value for the given [flag] or `null` if the scene framework isn't enabled * or if the flag value doesn't need to be overridden. Loading @@ -49,10 +53,18 @@ constructor( return null } val transitionState = interactor.get().transitionState.value val transitionState = sceneInteractor.get().transitionState.value val idleTransitionStateOrNull = transitionState as? ObservableTransitionState.Idle val currentSceneOrNull = idleTransitionStateOrNull?.scene return currentSceneOrNull?.let { sceneKey -> EvaluatorByFlag[flag]?.invoke(sceneKey) } val invisibleDueToOcclusion = occlusionInteractor.get().invisibleDueToOcclusion.value return currentSceneOrNull?.let { sceneKey -> EvaluatorByFlag[flag]?.invoke( SceneContainerPluginState( scene = sceneKey, invisibleDueToOcclusion = invisibleDueToOcclusion, ) ) } } companion object { Loading @@ -67,12 +79,24 @@ constructor( * to be overridden by the scene framework. */ val EvaluatorByFlag = mapOf<Int, (SceneKey) -> Boolean>( SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE to { it != Scenes.Gone }, SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED to { it == Scenes.Shade }, SYSUI_STATE_QUICK_SETTINGS_EXPANDED to { it == Scenes.QuickSettings }, SYSUI_STATE_BOUNCER_SHOWING to { it == Scenes.Bouncer }, SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING to { it == Scenes.Lockscreen }, mapOf<Int, (SceneContainerPluginState) -> Boolean>( SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE to { it.scene != Scenes.Gone }, SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED to { it.scene == Scenes.Shade }, SYSUI_STATE_QUICK_SETTINGS_EXPANDED to { it.scene == Scenes.QuickSettings }, SYSUI_STATE_BOUNCER_SHOWING to { it.scene == Scenes.Bouncer }, SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING to { it.scene == Scenes.Lockscreen && !it.invisibleDueToOcclusion }, SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED to { it.scene == Scenes.Lockscreen && it.invisibleDueToOcclusion }, ) } data class SceneContainerPluginState( val scene: SceneKey, val invisibleDueToOcclusion: Boolean, ) } packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt +59 −13 Original line number Diff line number Diff line Loading @@ -19,45 +19,91 @@ package com.android.systemui.scene.domain.interactor import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.KeyguardOcclusionInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn /** Encapsulates logic regarding the occlusion state of the scene container. */ @SysUISingleton class SceneContainerOcclusionInteractor @Inject constructor( @Application applicationScope: CoroutineScope, keyguardOcclusionInteractor: KeyguardOcclusionInteractor, sceneInteractor: SceneInteractor, keyguardTransitionInteractor: KeyguardTransitionInteractor, ) { /** Whether a show-when-locked activity is at the top of the current activity stack. */ private val isOccludingActivityShown: StateFlow<Boolean> = keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop.stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), initialValue = false, ) /** * Whether the scene container should become invisible due to "occlusion" by an in-foreground * "show when locked" activity. * Whether AOD is fully shown (not transitioning) or partially shown during a transition to/from * AOD. */ val invisibleDueToOcclusion: Flow<Boolean> = combine( keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop, sceneInteractor.transitionState, private val isAodFullyOrPartiallyShown: StateFlow<Boolean> = keyguardTransitionInteractor .transitionValue(KeyguardState.AOD) .onStart { emit(0f) } .map { it > 0 } .distinctUntilChanged(), .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), initialValue = false, ) /** * Whether the scene container should become invisible due to "occlusion" by an in-foreground * "show when locked" activity. */ val invisibleDueToOcclusion: StateFlow<Boolean> = combine( isOccludingActivityShown, sceneInteractor.transitionState, isAodFullyOrPartiallyShown, ) { isOccludingActivityShown, sceneTransitionState, isAodFullyOrPartiallyShown -> isOccludingActivityShown && invisibleDueToOcclusion( isOccludingActivityShown = isOccludingActivityShown, sceneTransitionState = sceneTransitionState, isAodFullyOrPartiallyShown = isAodFullyOrPartiallyShown, ) } .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), initialValue = invisibleDueToOcclusion( isOccludingActivityShown = isOccludingActivityShown.value, sceneTransitionState = sceneInteractor.transitionState.value, isAodFullyOrPartiallyShown = isAodFullyOrPartiallyShown.value, ), ) private fun invisibleDueToOcclusion( isOccludingActivityShown: Boolean, sceneTransitionState: ObservableTransitionState, isAodFullyOrPartiallyShown: Boolean, ): Boolean { return isOccludingActivityShown && // Cannot be occluded in AOD. !isAodFullyOrPartiallyShown && // Only some scenes can be occluded. sceneTransitionState.canBeOccluded } .distinctUntilChanged() private val ObservableTransitionState.canBeOccluded: Boolean get() = Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +1 −0 Original line number Diff line number Diff line Loading @@ -261,6 +261,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { shadeInteractor = kosmos.shadeInteractor, footerActionsController = kosmos.footerActionsController, footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory, sceneInteractor = sceneInteractor, ) kosmos.fakeDeviceEntryRepository.setUnlocked(false) Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +44 −2 Original line number Diff line number Diff line Loading @@ -42,7 +42,7 @@ import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepo import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.kosmos.testScope import com.android.systemui.model.SysUiState import com.android.systemui.model.sysUiState import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest import com.android.systemui.power.domain.interactor.PowerInteractorFactory Loading @@ -51,6 +51,7 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource import com.android.systemui.shared.system.QuickStepContract import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository Loading Loading @@ -78,6 +79,7 @@ import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.never import org.mockito.Mockito.spy import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations Loading @@ -99,7 +101,7 @@ class SceneContainerStartableTest : SysuiTestCase() { private val faceAuthRepository by lazy { kosmos.fakeDeviceEntryFaceAuthRepository } private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor } private val keyguardInteractor by lazy { kosmos.keyguardInteractor } private val sysUiState: SysUiState = mock() private val sysUiState = spy(kosmos.sysUiState) private val falsingCollector: FalsingCollector = mock() private val powerInteractor = PowerInteractorFactory.create().powerInteractor private val fakeSceneDataSource = kosmos.fakeSceneDataSource Loading Loading @@ -397,6 +399,46 @@ class SceneContainerStartableTest : SysuiTestCase() { } } @Test fun hydrateSystemUiState_onLockscreen_basedOnOcclusion() = testScope.runTest { prepareState( initialSceneKey = Scenes.Lockscreen, ) underTest.start() runCurrent() clearInvocations(sysUiState) kosmos.keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop( true, mock() ) runCurrent() assertThat( sysUiState.flags and QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED != 0 ) .isTrue() assertThat( sysUiState.flags and QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING != 0 ) .isFalse() kosmos.keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(false) runCurrent() assertThat( sysUiState.flags and QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED != 0 ) .isFalse() assertThat( sysUiState.flags and QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING != 0 ) .isTrue() } @Test fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNone() = testScope.runTest { Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt +3 −17 Original line number Diff line number Diff line Loading @@ -129,6 +129,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { shadeInteractor = kosmos.shadeInteractor, footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory, footerActionsController = kosmos.footerActionsController, sceneInteractor = kosmos.sceneInteractor, ) } Loading Loading @@ -215,22 +216,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { } @Test fun onContentClicked_deviceUnlocked_switchesToGone() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene) kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pin ) kosmos.fakeDeviceEntryRepository.setUnlocked(true) runCurrent() underTest.onContentClicked() assertThat(currentScene).isEqualTo(Scenes.Gone) } @Test fun onContentClicked_deviceLockedSecurely_switchesToBouncer() = fun onContentClicked_deviceLockedSecurely_switchesToLockscreen() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene) kosmos.fakeAuthenticationRepository.setAuthenticationMethod( Loading @@ -241,7 +227,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { underTest.onContentClicked() assertThat(currentScene).isEqualTo(Scenes.Bouncer) assertThat(currentScene).isEqualTo(Scenes.Lockscreen) } @Test Loading
packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt +33 −9 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.systemui.model import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.systemui.dagger.SysUISingleton import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes Loading @@ -27,6 +28,7 @@ import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICA import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED import dagger.Lazy import javax.inject.Inject Loading @@ -38,8 +40,10 @@ import javax.inject.Inject class SceneContainerPlugin @Inject constructor( private val interactor: Lazy<SceneInteractor>, private val sceneInteractor: Lazy<SceneInteractor>, private val occlusionInteractor: Lazy<SceneContainerOcclusionInteractor>, ) { /** * Returns an override value for the given [flag] or `null` if the scene framework isn't enabled * or if the flag value doesn't need to be overridden. Loading @@ -49,10 +53,18 @@ constructor( return null } val transitionState = interactor.get().transitionState.value val transitionState = sceneInteractor.get().transitionState.value val idleTransitionStateOrNull = transitionState as? ObservableTransitionState.Idle val currentSceneOrNull = idleTransitionStateOrNull?.scene return currentSceneOrNull?.let { sceneKey -> EvaluatorByFlag[flag]?.invoke(sceneKey) } val invisibleDueToOcclusion = occlusionInteractor.get().invisibleDueToOcclusion.value return currentSceneOrNull?.let { sceneKey -> EvaluatorByFlag[flag]?.invoke( SceneContainerPluginState( scene = sceneKey, invisibleDueToOcclusion = invisibleDueToOcclusion, ) ) } } companion object { Loading @@ -67,12 +79,24 @@ constructor( * to be overridden by the scene framework. */ val EvaluatorByFlag = mapOf<Int, (SceneKey) -> Boolean>( SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE to { it != Scenes.Gone }, SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED to { it == Scenes.Shade }, SYSUI_STATE_QUICK_SETTINGS_EXPANDED to { it == Scenes.QuickSettings }, SYSUI_STATE_BOUNCER_SHOWING to { it == Scenes.Bouncer }, SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING to { it == Scenes.Lockscreen }, mapOf<Int, (SceneContainerPluginState) -> Boolean>( SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE to { it.scene != Scenes.Gone }, SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED to { it.scene == Scenes.Shade }, SYSUI_STATE_QUICK_SETTINGS_EXPANDED to { it.scene == Scenes.QuickSettings }, SYSUI_STATE_BOUNCER_SHOWING to { it.scene == Scenes.Bouncer }, SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING to { it.scene == Scenes.Lockscreen && !it.invisibleDueToOcclusion }, SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED to { it.scene == Scenes.Lockscreen && it.invisibleDueToOcclusion }, ) } data class SceneContainerPluginState( val scene: SceneKey, val invisibleDueToOcclusion: Boolean, ) }
packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt +59 −13 Original line number Diff line number Diff line Loading @@ -19,45 +19,91 @@ package com.android.systemui.scene.domain.interactor import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.KeyguardOcclusionInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn /** Encapsulates logic regarding the occlusion state of the scene container. */ @SysUISingleton class SceneContainerOcclusionInteractor @Inject constructor( @Application applicationScope: CoroutineScope, keyguardOcclusionInteractor: KeyguardOcclusionInteractor, sceneInteractor: SceneInteractor, keyguardTransitionInteractor: KeyguardTransitionInteractor, ) { /** Whether a show-when-locked activity is at the top of the current activity stack. */ private val isOccludingActivityShown: StateFlow<Boolean> = keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop.stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), initialValue = false, ) /** * Whether the scene container should become invisible due to "occlusion" by an in-foreground * "show when locked" activity. * Whether AOD is fully shown (not transitioning) or partially shown during a transition to/from * AOD. */ val invisibleDueToOcclusion: Flow<Boolean> = combine( keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop, sceneInteractor.transitionState, private val isAodFullyOrPartiallyShown: StateFlow<Boolean> = keyguardTransitionInteractor .transitionValue(KeyguardState.AOD) .onStart { emit(0f) } .map { it > 0 } .distinctUntilChanged(), .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), initialValue = false, ) /** * Whether the scene container should become invisible due to "occlusion" by an in-foreground * "show when locked" activity. */ val invisibleDueToOcclusion: StateFlow<Boolean> = combine( isOccludingActivityShown, sceneInteractor.transitionState, isAodFullyOrPartiallyShown, ) { isOccludingActivityShown, sceneTransitionState, isAodFullyOrPartiallyShown -> isOccludingActivityShown && invisibleDueToOcclusion( isOccludingActivityShown = isOccludingActivityShown, sceneTransitionState = sceneTransitionState, isAodFullyOrPartiallyShown = isAodFullyOrPartiallyShown, ) } .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), initialValue = invisibleDueToOcclusion( isOccludingActivityShown = isOccludingActivityShown.value, sceneTransitionState = sceneInteractor.transitionState.value, isAodFullyOrPartiallyShown = isAodFullyOrPartiallyShown.value, ), ) private fun invisibleDueToOcclusion( isOccludingActivityShown: Boolean, sceneTransitionState: ObservableTransitionState, isAodFullyOrPartiallyShown: Boolean, ): Boolean { return isOccludingActivityShown && // Cannot be occluded in AOD. !isAodFullyOrPartiallyShown && // Only some scenes can be occluded. sceneTransitionState.canBeOccluded } .distinctUntilChanged() private val ObservableTransitionState.canBeOccluded: Boolean get() = Loading