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

Commit 80b546e6 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "[flexiglass] Occlusion support." into main

parents 280dc548 75584ff6
Loading
Loading
Loading
Loading
+47 −10
Original line number Diff line number Diff line
@@ -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()
        }
}
+2 −0
Original line number Diff line number Diff line
@@ -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()

+270 −0
Original line number Diff line number Diff line
/*
 * 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()
    }
}
+25 −0
Original line number Diff line number Diff line
@@ -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,
            )
    }

@@ -203,6 +206,28 @@ class SceneContainerStartableTest : SysuiTestCase() {
            assertThat(isVisible).isTrue()
        }

    @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 {
+18 −8
Original line number Diff line number Diff line
@@ -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
Loading