diff options
author | 2024-03-28 03:48:49 +0000 | |
---|---|---|
committer | 2024-03-28 03:48:49 +0000 | |
commit | 80b546e60bc9ad69c596de1836daac878d3fb921 (patch) | |
tree | e93f4be85666edcd7cecaea387c081bb7e082485 | |
parent | 280dc5486760ea1e226776bc8ee592b1dd533615 (diff) | |
parent | 75584ff6da827aa0ed13d1da2b3b3d44d12da977 (diff) |
Merge "[flexiglass] Occlusion support." into main
11 files changed, 525 insertions, 47 deletions
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt index 8a77ed2130a9..056a401a2644 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt @@ -30,17 +30,24 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.keyguard.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues +import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository +import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.kosmos.testScope +import com.android.systemui.power.domain.interactor.PowerInteractor 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.powerInteractor @@ -49,22 +56,33 @@ import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue -import kotlin.test.Test +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class KeyguardOcclusionInteractorTest : SysuiTestCase() { + private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val underTest = kosmos.keyguardOcclusionInteractor - private val powerInteractor = kosmos.powerInteractor - private val transitionRepository = kosmos.fakeKeyguardTransitionRepository + + private lateinit var underTest: KeyguardOcclusionInteractor + private lateinit var powerInteractor: PowerInteractor + private lateinit var transitionRepository: FakeKeyguardTransitionRepository + + @Before + fun setUp() { + powerInteractor = kosmos.powerInteractor + transitionRepository = kosmos.fakeKeyguardTransitionRepository + underTest = kosmos.keyguardOcclusionInteractor + } @Test - fun testTransitionFromPowerGesture_whileGoingToSleep_isTrue() = + fun transitionFromPowerGesture_whileGoingToSleep_isTrue() = testScope.runTest { powerInteractor.setAwakeForTest() transitionRepository.sendTransitionSteps( @@ -81,7 +99,7 @@ class KeyguardOcclusionInteractorTest : SysuiTestCase() { } @Test - fun testTransitionFromPowerGesture_whileAsleep_isTrue() = + fun transitionFromPowerGesture_whileAsleep_isTrue() = testScope.runTest { powerInteractor.setAwakeForTest() transitionRepository.sendTransitionSteps( @@ -97,7 +115,7 @@ class KeyguardOcclusionInteractorTest : SysuiTestCase() { } @Test - fun testTransitionFromPowerGesture_whileWaking_isFalse() = + fun transitionFromPowerGesture_whileWaking_isFalse() = testScope.runTest { powerInteractor.setAwakeForTest() transitionRepository.sendTransitionSteps( @@ -119,7 +137,7 @@ class KeyguardOcclusionInteractorTest : SysuiTestCase() { } @Test - fun testTransitionFromPowerGesture_whileAwake_isFalse() = + fun transitionFromPowerGesture_whileAwake_isFalse() = testScope.runTest { powerInteractor.setAwakeForTest() transitionRepository.sendTransitionSteps( @@ -140,7 +158,7 @@ class KeyguardOcclusionInteractorTest : SysuiTestCase() { } @Test - fun testShowWhenLockedActivityLaunchedFromPowerGesture_notTrueSecondTime() = + fun showWhenLockedActivityLaunchedFromPowerGesture_notTrueSecondTime() = testScope.runTest { val values by collectValues(underTest.showWhenLockedActivityLaunchedFromPowerGesture) powerInteractor.setAsleepForTest() @@ -187,7 +205,7 @@ class KeyguardOcclusionInteractorTest : SysuiTestCase() { } @Test - fun testShowWhenLockedActivityLaunchedFromPowerGesture_falseIfReturningToGone() = + fun showWhenLockedActivityLaunchedFromPowerGesture_falseIfReturningToGone() = testScope.runTest { val values by collectValues(underTest.showWhenLockedActivityLaunchedFromPowerGesture) powerInteractor.setAwakeForTest() @@ -221,4 +239,23 @@ class KeyguardOcclusionInteractorTest : SysuiTestCase() { false, ) } + + @Test + @EnableSceneContainer + fun occludingActivityWillDismissKeyguard() = + testScope.runTest { + val occludingActivityWillDismissKeyguard by + collectLastValue(underTest.occludingActivityWillDismissKeyguard) + assertThat(occludingActivityWillDismissKeyguard).isFalse() + + // Unlock device: + kosmos.fakeDeviceEntryRepository.setUnlocked(true) + runCurrent() + assertThat(occludingActivityWillDismissKeyguard).isTrue() + + // Re-lock device: + kosmos.fakeDeviceEntryRepository.setUnlocked(false) + runCurrent() + assertThat(occludingActivityWillDismissKeyguard).isFalse() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index c80835d9b2b3..efbdb7d466d1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -64,6 +64,7 @@ import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.qs.footerActionsController import com.android.systemui.qs.footerActionsViewModelFactory import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter +import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.domain.startable.SceneContainerStartable import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags @@ -286,6 +287,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { deviceProvisioningInteractor = kosmos.deviceProvisioningInteractor, centralSurfaces = mock(), headsUpInteractor = kosmos.headsUpNotificationInteractor, + occlusionInteractor = kosmos.sceneContainerOcclusionInteractor, ) startable.start() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorTest.kt new file mode 100644 index 000000000000..c3366ad21e2a --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorTest.kt @@ -0,0 +1,270 @@ +/* + * 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.ObservableTransitionState +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.scene.shared.model.sceneDataSource +import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor +import com.android.systemui.testKosmos +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth.assertWithMessage +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf +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 SceneContainerOcclusionInteractorTest : SysuiTestCase() { + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val keyguardOcclusionInteractor = kosmos.keyguardOcclusionInteractor + private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository + private val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor + private val mutableTransitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(Scenes.Lockscreen) + ) + private val sceneInteractor = + kosmos.sceneInteractor.apply { setTransitionState(mutableTransitionState) } + private val sceneDataSource = + kosmos.sceneDataSource.apply { changeScene(toScene = Scenes.Lockscreen) } + + private val underTest = kosmos.sceneContainerOcclusionInteractor + + @Test + fun invisibleDueToOcclusion() = + testScope.runTest { + val invisibleDueToOcclusion by collectLastValue(underTest.invisibleDueToOcclusion) + val keyguardState by collectLastValue(keyguardTransitionInteractor.currentKeyguardState) + + // Assert that we have the desired preconditions: + assertThat(keyguardState).isEqualTo(KeyguardState.LOCKSCREEN) + assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Lockscreen) + assertThat(sceneInteractor.transitionState.value) + .isEqualTo(ObservableTransitionState.Idle(Scenes.Lockscreen)) + assertWithMessage("Should start unoccluded").that(invisibleDueToOcclusion).isFalse() + + // Actual testing starts here: + showOccludingActivity() + assertWithMessage("Should become occluded when occluding activity is shown") + .that(invisibleDueToOcclusion) + .isTrue() + + transitionIntoAod { + assertWithMessage("Should become unoccluded when transitioning into AOD") + .that(invisibleDueToOcclusion) + .isFalse() + } + assertWithMessage("Should stay unoccluded when in AOD") + .that(invisibleDueToOcclusion) + .isFalse() + + transitionOutOfAod { + assertWithMessage("Should remain unoccluded while transitioning away from AOD") + .that(invisibleDueToOcclusion) + .isFalse() + } + assertWithMessage("Should become occluded now that no longer in AOD") + .that(invisibleDueToOcclusion) + .isTrue() + + expandShade { + assertWithMessage("Should become unoccluded once shade begins to expand") + .that(invisibleDueToOcclusion) + .isFalse() + } + assertWithMessage("Should be unoccluded when shade is fully expanded") + .that(invisibleDueToOcclusion) + .isFalse() + + collapseShade { + assertWithMessage("Should remain unoccluded while shade is collapsing") + .that(invisibleDueToOcclusion) + .isFalse() + } + assertWithMessage("Should become occluded now that shade is fully collapsed") + .that(invisibleDueToOcclusion) + .isTrue() + + hideOccludingActivity() + assertWithMessage("Should become unoccluded once the occluding activity is hidden") + .that(invisibleDueToOcclusion) + .isFalse() + } + + /** Simulates the appearance of a show-when-locked `Activity` in the foreground. */ + private fun TestScope.showOccludingActivity() { + keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop( + showWhenLockedActivityOnTop = true, + taskInfo = mock(), + ) + runCurrent() + } + + /** Simulates the disappearance of a show-when-locked `Activity` from the foreground. */ + private fun TestScope.hideOccludingActivity() { + keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop( + showWhenLockedActivityOnTop = false, + ) + runCurrent() + } + + /** Simulates a user-driven gradual expansion of the shade. */ + private fun TestScope.expandShade( + assertMidTransition: () -> Unit = {}, + ) { + val progress = MutableStateFlow(0f) + mutableTransitionState.value = + ObservableTransitionState.Transition( + fromScene = sceneDataSource.currentScene.value, + toScene = Scenes.Shade, + progress = progress, + isInitiatedByUserInput = true, + isUserInputOngoing = flowOf(true), + ) + runCurrent() + + progress.value = 0.5f + runCurrent() + assertMidTransition() + + progress.value = 1f + runCurrent() + + mutableTransitionState.value = ObservableTransitionState.Idle(Scenes.Shade) + runCurrent() + } + + /** Simulates a user-driven gradual collapse of the shade. */ + private fun TestScope.collapseShade( + assertMidTransition: () -> Unit = {}, + ) { + val progress = MutableStateFlow(0f) + mutableTransitionState.value = + ObservableTransitionState.Transition( + fromScene = Scenes.Shade, + toScene = Scenes.Lockscreen, + progress = progress, + isInitiatedByUserInput = true, + isUserInputOngoing = flowOf(true), + ) + runCurrent() + + progress.value = 0.5f + runCurrent() + assertMidTransition() + + progress.value = 1f + runCurrent() + + mutableTransitionState.value = ObservableTransitionState.Idle(Scenes.Lockscreen) + runCurrent() + } + + /** Simulates a transition into AOD. */ + private suspend fun TestScope.transitionIntoAod( + assertMidTransition: () -> Unit = {}, + ) { + val currentKeyguardState = keyguardTransitionInteractor.getCurrentState() + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = currentKeyguardState, + to = KeyguardState.AOD, + value = 0f, + transitionState = TransitionState.STARTED, + ) + ) + runCurrent() + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = currentKeyguardState, + to = KeyguardState.AOD, + value = 0.5f, + transitionState = TransitionState.RUNNING, + ) + ) + runCurrent() + assertMidTransition() + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = currentKeyguardState, + to = KeyguardState.AOD, + value = 1f, + transitionState = TransitionState.FINISHED, + ) + ) + runCurrent() + } + + /** Simulates a transition away from AOD. */ + private suspend fun TestScope.transitionOutOfAod( + assertMidTransition: () -> Unit = {}, + ) { + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + value = 0f, + transitionState = TransitionState.STARTED, + ) + ) + runCurrent() + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + value = 0.5f, + transitionState = TransitionState.RUNNING, + ) + ) + runCurrent() + assertMidTransition() + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + value = 1f, + transitionState = TransitionState.FINISHED, + ) + ) + runCurrent() + } +} 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 594543b88b17..605e5c067eab 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 @@ -46,11 +46,13 @@ 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 +import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor 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.statusbar.NotificationShadeWindowController +import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository @@ -128,6 +130,7 @@ class SceneContainerStartableTest : SysuiTestCase() { deviceProvisioningInteractor = kosmos.deviceProvisioningInteractor, centralSurfaces = centralSurfaces, headsUpInteractor = kosmos.headsUpNotificationInteractor, + occlusionInteractor = kosmos.sceneContainerOcclusionInteractor, ) } @@ -204,6 +207,28 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test + fun hydrateVisibility_basedOnOcclusion() = + testScope.runTest { + val isVisible by collectLastValue(sceneInteractor.isVisible) + prepareState( + isDeviceUnlocked = true, + initialSceneKey = Scenes.Lockscreen, + ) + + underTest.start() + assertThat(isVisible).isTrue() + + kosmos.keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop( + true, + mock() + ) + assertThat(isVisible).isFalse() + + kosmos.keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(false) + assertThat(isVisible).isTrue() + } + + @Test fun startsInLockscreenScene() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt index 9aa2202b4100..03ed5675a77a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt @@ -19,13 +19,17 @@ package com.android.systemui.keyguard.domain.interactor import android.app.ActivityManager.RunningTaskInfo import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor import com.android.systemui.keyguard.data.repository.KeyguardOcclusionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.util.kotlin.sample +import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map @@ -45,11 +49,12 @@ import kotlinx.coroutines.flow.stateIn class KeyguardOcclusionInteractor @Inject constructor( - @Application scope: CoroutineScope, - val repository: KeyguardOcclusionRepository, - val powerInteractor: PowerInteractor, - val transitionInteractor: KeyguardTransitionInteractor, - val keyguardInteractor: KeyguardInteractor, + @Application applicationScope: CoroutineScope, + private val repository: KeyguardOcclusionRepository, + private val powerInteractor: PowerInteractor, + private val transitionInteractor: KeyguardTransitionInteractor, + keyguardInteractor: KeyguardInteractor, + deviceUnlockedInteractor: Lazy<DeviceUnlockedInteractor>, ) { val showWhenLockedActivityInfo = repository.showWhenLockedActivityInfo.asStateFlow() @@ -94,14 +99,19 @@ constructor( // Emit false once that activity goes away. isShowWhenLockedActivityOnTop.filter { !it }.map { false } ) - .stateIn(scope, SharingStarted.Eagerly, false) + .stateIn(applicationScope, SharingStarted.Eagerly, false) /** * Whether launching an occluding activity will automatically dismiss keyguard. This happens if * the keyguard is dismissable. */ - val occludingActivityWillDismissKeyguard = - keyguardInteractor.isKeyguardDismissible.stateIn(scope, SharingStarted.Eagerly, false) + val occludingActivityWillDismissKeyguard: StateFlow<Boolean> = + if (SceneContainerFlag.isEnabled) { + deviceUnlockedInteractor.get().isDeviceUnlocked + } else { + keyguardInteractor.isKeyguardDismissible + } + .stateIn(scope = applicationScope, SharingStarted.Eagerly, false) /** * Called to let System UI know that WM says a SHOW_WHEN_LOCKED activity is on top (or no longer diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt new file mode 100644 index 000000000000..a82391643e11 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt @@ -0,0 +1,86 @@ +/* + * 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.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey +import com.android.systemui.dagger.SysUISingleton +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.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart + +/** Encapsulates logic regarding the occlusion state of the scene container. */ +@SysUISingleton +class SceneContainerOcclusionInteractor +@Inject +constructor( + keyguardOcclusionInteractor: KeyguardOcclusionInteractor, + sceneInteractor: SceneInteractor, + keyguardTransitionInteractor: KeyguardTransitionInteractor, +) { + /** + * Whether the scene container should become invisible due to "occlusion" by an in-foreground + * "show when locked" activity. + */ + val invisibleDueToOcclusion: Flow<Boolean> = + combine( + keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop, + sceneInteractor.transitionState, + keyguardTransitionInteractor + .transitionValue(KeyguardState.AOD) + .onStart { emit(0f) } + .map { it > 0 } + .distinctUntilChanged(), + ) { isOccludingActivityShown, sceneTransitionState, isAodFullyOrPartiallyShown -> + isOccludingActivityShown && + !isAodFullyOrPartiallyShown && + sceneTransitionState.canBeOccluded + } + .distinctUntilChanged() + + private val ObservableTransitionState.canBeOccluded: Boolean + get() = + when (this) { + is ObservableTransitionState.Idle -> scene.canBeOccluded + is ObservableTransitionState.Transition -> + fromScene.canBeOccluded && toScene.canBeOccluded + } + + /** + * Whether the scene can be occluded by a "show when locked" activity. Some scenes should, on + * principle not be occlude-able because they render as if they are expanding on top of the + * occluding activity. + */ + private val SceneKey.canBeOccluded: Boolean + get() = + when (this) { + Scenes.Bouncer -> true + Scenes.Communal -> true + Scenes.Gone -> true + Scenes.Lockscreen -> true + Scenes.QuickSettings -> false + Scenes.Shade -> false + else -> error("SceneKey \"$this\" doesn't have a mapping for canBeOccluded!") + } +} 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 42b41f88e155..0e4049bbd21e 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 @@ -40,6 +40,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.SceneContainerOcclusionInteractor import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.scene.shared.logger.SceneLogger @@ -94,6 +95,7 @@ constructor( private val deviceProvisioningInteractor: DeviceProvisioningInteractor, private val centralSurfaces: CentralSurfaces, private val headsUpInteractor: HeadsUpNotificationInteractor, + private val occlusionInteractor: SceneContainerOcclusionInteractor, ) : CoreStartable { override fun start() { @@ -130,32 +132,36 @@ constructor( .distinctUntilChanged() .flatMapLatest { isAllowedToBeVisible -> if (isAllowedToBeVisible) { - sceneInteractor.transitionState - .mapNotNull { state -> - when (state) { - is ObservableTransitionState.Idle -> { - if (state.scene != Scenes.Gone) { - true to "scene is not Gone" - } else { - false to "scene is Gone" + combine( + sceneInteractor.transitionState.mapNotNull { state -> + when (state) { + is ObservableTransitionState.Idle -> { + if (state.scene != Scenes.Gone) { + true to "scene is not Gone" + } else { + false to "scene is Gone" + } } - } - is ObservableTransitionState.Transition -> { - if (state.fromScene == Scenes.Gone) { - true to "scene transitioning away from Gone" - } else { - null + is ObservableTransitionState.Transition -> { + if (state.fromScene == Scenes.Gone) { + true to "scene transitioning away from Gone" + } else { + null + } } } - } - } - .combine(headsUpInteractor.isHeadsUpOrAnimatingAway) { + }, + headsUpInteractor.isHeadsUpOrAnimatingAway, + occlusionInteractor.invisibleDueToOcclusion, + ) { visibilityForTransitionState, - isHeadsUpOrAnimatingAway -> - if (isHeadsUpOrAnimatingAway) { - true to "showing a HUN" - } else { - visibilityForTransitionState + isHeadsUpOrAnimatingAway, + invisibleDueToOcclusion, + -> + when { + isHeadsUpOrAnimatingAway -> true to "showing a HUN" + invisibleDueToOcclusion -> false to "invisible due to occlusion" + else -> visibilityForTransitionState } } .distinctUntilChanged() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt index 7bef01a7a5ce..3926f92b44e8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt @@ -48,6 +48,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat import com.android.systemui.kosmos.testScope +import com.android.systemui.power.domain.interactor.PowerInteractor 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.powerInteractor @@ -73,13 +74,17 @@ class FromAodTransitionInteractorTest : SysuiTestCase() { } private val testScope = kosmos.testScope - private val underTest = kosmos.fromAodTransitionInteractor + private lateinit var underTest: FromAodTransitionInteractor - private val powerInteractor = kosmos.powerInteractor - private val transitionRepository = kosmos.fakeKeyguardTransitionRepository + private lateinit var powerInteractor: PowerInteractor + private lateinit var transitionRepository: FakeKeyguardTransitionRepository @Before fun setup() { + powerInteractor = kosmos.powerInteractor + transitionRepository = kosmos.fakeKeyguardTransitionRepository + underTest = kosmos.fromAodTransitionInteractor + underTest.start() // Transition to AOD and set the power interactor asleep. diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt index 258dbf3efbae..cded2a4ec27c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt @@ -51,6 +51,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat import com.android.systemui.kosmos.testScope +import com.android.systemui.power.domain.interactor.PowerInteractor 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.powerInteractor @@ -77,13 +78,17 @@ class FromDozingTransitionInteractorTest : SysuiTestCase() { } private val testScope = kosmos.testScope - private val underTest = kosmos.fromDozingTransitionInteractor + private lateinit var underTest: FromDozingTransitionInteractor - private val powerInteractor = kosmos.powerInteractor - private val transitionRepository = kosmos.fakeKeyguardTransitionRepository + private lateinit var powerInteractor: PowerInteractor + private lateinit var transitionRepository: FakeKeyguardTransitionRepository @Before fun setup() { + powerInteractor = kosmos.powerInteractor + transitionRepository = kosmos.fakeKeyguardTransitionRepository + underTest = kosmos.fromDozingTransitionInteractor + underTest.start() // Transition to DOZING and set the power interactor asleep. diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorKosmos.kt new file mode 100644 index 000000000000..b32960a73178 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorKosmos.kt @@ -0,0 +1,30 @@ +/* + * 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.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor + +val Kosmos.sceneContainerOcclusionInteractor by Fixture { + SceneContainerOcclusionInteractor( + keyguardOcclusionInteractor = keyguardOcclusionInteractor, + sceneInteractor = sceneInteractor, + keyguardTransitionInteractor = keyguardTransitionInteractor, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardOcclusionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardOcclusionInteractorKosmos.kt index d79374021968..a90a9ffc8a33 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardOcclusionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardOcclusionInteractorKosmos.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.domain.interactor +import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardOcclusionInteractor import com.android.systemui.keyguard.domain.interactor.keyguardInteractor @@ -27,10 +28,11 @@ import com.android.systemui.power.domain.interactor.powerInteractor val Kosmos.keyguardOcclusionInteractor by Kosmos.Fixture { KeyguardOcclusionInteractor( - scope = testScope.backgroundScope, + applicationScope = testScope.backgroundScope, repository = keyguardOcclusionRepository, powerInteractor = powerInteractor, transitionInteractor = keyguardTransitionInteractor, keyguardInteractor = keyguardInteractor, + deviceUnlockedInteractor = { deviceUnlockedInteractor }, ) } |