summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Alejandro Nijamkin <nijamkin@google.com> 2024-03-18 17:26:00 -0700
committer Alejandro Nijamkin <nijamkin@google.com> 2024-03-27 16:53:47 -0700
commit75584ff6da827aa0ed13d1da2b3b3d44d12da977 (patch)
tree42399259654f468f5f8d9a461fe771df73c48000
parentc3d7b6367070147a53837e90cf412b1f7c8d4bf7 (diff)
[flexiglass] Occlusion support.
Adds support for occlusion in Flexiglass. System UI's window is rendered on top of all other windows. This is by design because the keyguard (lockscreen, bouncer) must be rendered on top of any other activity window to "lock" the device and also because the shade (notification or QS shade) must be expandable above any other activity window. Sometimes, however, we can show an activity "on top" of the locked keyguard; this is a developer-facing API that allows them to mark their activities as "shown on lockscreen" in their app's AndroidManifest.xml file (if they have the right permission). When an activity like that is shown, we simulate "occlusion" of the System UI window by hiding our window-view, which shows that activity. That said, when the user expands the shade or QS, our window needs to be shown again. Similarly, in AOD, our window should again be shown. This CL adds that logic to Flexiglass. The majority of the logic is in the new SceneContainerOcclusionInteractor which reuses the KeyguardOcclusionInteractor (with one minor change). The visibility-setting logic in our SceneContainerStartable is also altered to take that new state into account. Still missing: 1. Transition to bouncer when the "camera roll" button is clicked inside the camera app (this requires compliance with a signal from KeyguardService, most likely) 2. Inability to navigate home or back while the occluding activity is showing Bug: 308001302 Test: manually verified that showing an occluding activity (used the camera lockscreen shortcut) hides system UI Test: manually verified that expanding the shade or QS scenes when occluded, shows those scenes and that collapsing them re-occludes system UI Test: manually verified that AOD also shows system UI, even when the camera activity was on top Test: Launched Maps Navigation while unlocked, turned the screen off, and then back on (ended on occluding activity, tests waking to occluded) Test: Set a timer, turned the screen off, waited for the timer activity to open over lockscreen, dismissed it (tests manually interacting with the device to end an occluding activity) Test: Received a phone call and didn't answer it (this tests the occluding activity ending itself) Test: added unit and integration tests Flag: ACONFIG com.android.systemui.scene_container DEVELOPMENT Change-Id: If004ecae201d8193e598030a89a0dc589bd6b4d4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt)57
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorTest.kt270
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt86
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt50
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt11
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorKosmos.kt30
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardOcclusionInteractorKosmos.kt4
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 307778bf7e0f..05c81496c3dc 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
@@ -285,6 +286,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 },
)
}