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

Commit 3a1b19d8 authored by William Leshner's avatar William Leshner Committed by Android (Google) Code Review
Browse files

Merge "Transition to hub from screen off when applicable." into main

parents 9cda256f 41ec5cc5
Loading
Loading
Loading
Loading
+23 −0
Original line number Diff line number Diff line
@@ -27,6 +27,9 @@ import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.dock.dockManager
import com.android.systemui.dock.fakeDockManager
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.flags.featureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -66,6 +69,7 @@ class CommunalSceneStartableTest : SysuiTestCase() {
    fun setUp() {
        with(kosmos) {
            fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT)
            kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)

            underTest =
                CommunalSceneStartable(
@@ -76,6 +80,7 @@ class CommunalSceneStartableTest : SysuiTestCase() {
                        keyguardInteractor = keyguardInteractor,
                        systemSettings = fakeSettings,
                        notificationShadeWindowController = notificationShadeWindowController,
                        featureFlagsClassic = kosmos.fakeFeatureFlagsClassic,
                        applicationScope = applicationCoroutineScope,
                        bgScope = applicationCoroutineScope,
                        mainDispatcher = testDispatcher,
@@ -451,6 +456,24 @@ class CommunalSceneStartableTest : SysuiTestCase() {
            }
        }

    @Test
    fun transitionFromDozingToGlanceableHub_forcesCommunal() =
        with(kosmos) {
            testScope.runTest {
                val scene by collectLastValue(communalSceneInteractor.currentScene)
                communalSceneInteractor.changeScene(CommunalScenes.Blank)
                assertThat(scene).isEqualTo(CommunalScenes.Blank)

                fakeKeyguardTransitionRepository.sendTransitionSteps(
                    from = KeyguardState.DOZING,
                    to = KeyguardState.GLANCEABLE_HUB,
                    testScope = this
                )

                assertThat(scene).isEqualTo(CommunalScenes.Communal)
            }
        }

    private fun TestScope.updateDocked(docked: Boolean) =
        with(kosmos) {
            runCurrent()
+64 −0
Original line number Diff line number Diff line
@@ -34,12 +34,14 @@ package com.android.systemui.keyguard.domain.interactor

import android.os.PowerManager
import android.platform.test.annotations.EnableFlags
import android.service.dream.dreamManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -64,8 +66,10 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.reset
import org.mockito.Mockito.spy
import org.mockito.kotlin.whenever

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -118,6 +122,66 @@ class FromDozingTransitionInteractorTest : SysuiTestCase() {
                )
        }

    @Test
    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
    fun testTransitionToLockscreen_onPowerButtonPress_canDream_glanceableHubAvailable() =
        testScope.runTest {
            whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
            kosmos.setCommunalAvailable(true)
            runCurrent()

            powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON)
            runCurrent()

            // If dreaming is possible and communal is available, then we should transition to
            // GLANCEABLE_HUB when waking up due to power button press.
            assertThat(transitionRepository)
                .startedTransition(
                    from = KeyguardState.DOZING,
                    to = KeyguardState.GLANCEABLE_HUB,
                )
        }

    @Test
    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
    fun testTransitionToLockscreen_onPowerButtonPress_canNotDream_glanceableHubAvailable() =
        testScope.runTest {
            whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(false)
            kosmos.setCommunalAvailable(true)
            runCurrent()

            powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON)
            runCurrent()

            // If dreaming is NOT possible but communal is available, then we should transition to
            // LOCKSCREEN when waking up due to power button press.
            assertThat(transitionRepository)
                .startedTransition(
                    from = KeyguardState.DOZING,
                    to = KeyguardState.LOCKSCREEN,
                )
        }

    @Test
    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
    fun testTransitionToLockscreen_onPowerButtonPress_canNDream_glanceableHubNotAvailable() =
        testScope.runTest {
            whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
            kosmos.setCommunalAvailable(false)
            runCurrent()

            powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON)
            runCurrent()

            // If dreaming is possible but communal is NOT available, then we should transition to
            // LOCKSCREEN when waking up due to power button press.
            assertThat(transitionRepository)
                .startedTransition(
                    from = KeyguardState.DOZING,
                    to = KeyguardState.LOCKSCREEN,
                )
        }

    @Test
    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
    fun testTransitionToGlanceableHub_onWakeup_ifIdleOnCommunal_noOccludingActivity() =
+25 −7
Original line number Diff line number Diff line
@@ -18,7 +18,9 @@ package com.android.systemui.communal

import android.provider.Settings
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.TransitionKey
import com.android.systemui.CoreStartable
import com.android.systemui.Flags.communalHub
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
@@ -28,6 +30,8 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dock.DockManager
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -74,6 +78,7 @@ constructor(
    private val systemSettings: SystemSettings,
    centralSurfacesOpt: Optional<CentralSurfaces>,
    private val notificationShadeWindowController: NotificationShadeWindowController,
    private val featureFlagsClassic: FeatureFlagsClassic,
    @Application private val applicationScope: CoroutineScope,
    @Background private val bgScope: CoroutineScope,
    @Main private val mainDispatcher: CoroutineDispatcher,
@@ -86,13 +91,21 @@ constructor(

    private val centralSurfaces: CentralSurfaces? by centralSurfacesOpt

    private val flagEnabled: Boolean by lazy {
        featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub()
    }

    override fun start() {
        if (!flagEnabled) {
            return
        }

        // Handle automatically switching based on keyguard state.
        keyguardTransitionInteractor.startedKeyguardTransitionStep
            .mapLatest(::determineSceneAfterTransition)
            .filterNotNull()
            .onEach { nextScene ->
                communalSceneInteractor.changeScene(nextScene, CommunalTransitionKeys.SimpleFade)
            .onEach { (nextScene, nextTransition) ->
                communalSceneInteractor.changeScene(nextScene, nextTransition)
            }
            .launchIn(applicationScope)

@@ -188,7 +201,7 @@ constructor(

    private suspend fun determineSceneAfterTransition(
        lastStartedTransition: TransitionStep,
    ): SceneKey? {
    ): Pair<SceneKey, TransitionKey>? {
        val to = lastStartedTransition.to
        val from = lastStartedTransition.from
        val docked = dockManager.isDocked
@@ -201,22 +214,27 @@ constructor(
                // underneath the hub is shown. When launching activities over lockscreen, we only
                // change scenes once the activity launch animation is finished, so avoid
                // changing the scene here.
                CommunalScenes.Blank
                Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade)
            }
            to == KeyguardState.GLANCEABLE_HUB && from == KeyguardState.OCCLUDED -> {
                // When transitioning to the hub from an occluded state, fade out the hub without
                // doing any translation.
                CommunalScenes.Communal
                Pair(CommunalScenes.Communal, CommunalTransitionKeys.SimpleFade)
            }
            // Transitioning to Blank scene when entering the edit mode will be handled separately
            // with custom animations.
            to == KeyguardState.GONE && !communalInteractor.editModeOpen.value ->
                CommunalScenes.Blank
                Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade)
            !docked && !KeyguardState.deviceIsAwakeInState(to) -> {
                // If the user taps the screen and wakes the device within this timeout, we don't
                // want to dismiss the hub
                delay(AWAKE_DEBOUNCE_DELAY)
                CommunalScenes.Blank
                Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade)
            }
            from == KeyguardState.DOZING && to == KeyguardState.GLANCEABLE_HUB -> {
                // Make sure the communal hub is showing (immediately, not fading in) when
                // transitioning from dozing to hub.
                Pair(CommunalScenes.Communal, CommunalTransitionKeys.Immediately)
            }
            else -> null
        }
+44 −2
Original line number Diff line number Diff line
@@ -17,8 +17,11 @@
package com.android.systemui.keyguard.domain.interactor

import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.app.DreamManager
import com.android.app.animation.Interpolators
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -28,6 +31,7 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode.Companion.isWakeAndUnlock
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.util.kotlin.Utils.Companion.sample
import com.android.systemui.util.kotlin.sample
@@ -53,9 +57,11 @@ constructor(
    keyguardInteractor: KeyguardInteractor,
    powerInteractor: PowerInteractor,
    private val communalInteractor: CommunalInteractor,
    private val communalSceneInteractor: CommunalSceneInteractor,
    keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
    val deviceEntryRepository: DeviceEntryRepository,
    private val wakeToGoneInteractor: KeyguardWakeDirectlyToGoneInteractor,
    private val dreamManager: DreamManager,
) :
    TransitionInteractor(
        fromState = KeyguardState.DOZING,
@@ -115,6 +121,7 @@ constructor(
        }
    }

    @SuppressLint("MissingPermission")
    private fun listenForDozingToAny() {
        if (KeyguardWmStateRefactor.isEnabled) {
            return
@@ -126,7 +133,8 @@ constructor(
                .filterRelevantKeyguardStateAnd { isAwake -> isAwake }
                .sample(
                    keyguardInteractor.isKeyguardOccluded,
                    communalInteractor.isIdleOnCommunal,
                    communalInteractor.isCommunalAvailable,
                    communalSceneInteractor.isIdleOnCommunal,
                    canTransitionToGoneOnWake,
                    keyguardInteractor.primaryBouncerShowing,
                )
@@ -134,6 +142,7 @@ constructor(
                    (
                        _,
                        occluded,
                        isCommunalAvailable,
                        isIdleOnCommunal,
                        canTransitionToGoneOnWake,
                        primaryBouncerShowing) ->
@@ -163,6 +172,19 @@ constructor(
                        } else {
                            startTransitionTo(KeyguardState.GLANCEABLE_HUB)
                        }
                    } else if (
                        powerInteractor.detailedWakefulness.value.lastWakeReason ==
                            WakeSleepReason.POWER_BUTTON &&
                            isCommunalAvailable &&
                            dreamManager.canStartDreaming(true)
                    ) {
                        // This case handles tapping the power button to transition through
                        // dream -> off -> hub.
                        if (SceneContainerFlag.isEnabled) {
                            // TODO(b/336576536): Check if adaptation for scene framework is needed
                        } else {
                            startTransitionTo(KeyguardState.GLANCEABLE_HUB)
                        }
                    } else {
                        startTransitionTo(KeyguardState.LOCKSCREEN)
                    }
@@ -171,6 +193,7 @@ constructor(
    }

    /** Figure out what state to transition to when we awake from DOZING. */
    @SuppressLint("MissingPermission")
    private fun listenForWakeFromDozing() {
        if (!KeyguardWmStateRefactor.isEnabled) {
            return
@@ -180,7 +203,8 @@ constructor(
            powerInteractor.detailedWakefulness
                .filterRelevantKeyguardStateAnd { it.isAwake() }
                .sample(
                    communalInteractor.isIdleOnCommunal,
                    communalInteractor.isCommunalAvailable,
                    communalSceneInteractor.isIdleOnCommunal,
                    keyguardInteractor.biometricUnlockState,
                    wakeToGoneInteractor.canWakeDirectlyToGone,
                    keyguardInteractor.primaryBouncerShowing,
@@ -188,6 +212,7 @@ constructor(
                .collect {
                    (
                        _,
                        isCommunalAvailable,
                        isIdleOnCommunal,
                        biometricUnlockState,
                        canWakeDirectlyToGone,
@@ -227,6 +252,23 @@ constructor(
                                    ownerReason = "waking from dozing"
                                )
                            }
                        } else if (
                            powerInteractor.detailedWakefulness.value.lastWakeReason ==
                                WakeSleepReason.POWER_BUTTON &&
                                isCommunalAvailable &&
                                dreamManager.canStartDreaming(true)
                        ) {
                            // This case handles tapping the power button to transition through
                            // dream -> off -> hub.
                            if (SceneContainerFlag.isEnabled) {
                                // TODO(b/336576536): Check if adaptation for scene framework is
                                // needed
                            } else {
                                startTransitionTo(
                                    KeyguardState.GLANCEABLE_HUB,
                                    ownerReason = "waking from dozing"
                                )
                            }
                        } else {
                            startTransitionTo(
                                KeyguardState.LOCKSCREEN,
+4 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
package com.android.systemui

import android.app.ActivityManager
import android.app.DreamManager
import android.app.admin.DevicePolicyManager
import android.app.trust.TrustManager
import android.hardware.fingerprint.FingerprintManager
@@ -33,6 +34,7 @@ import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.biometrics.AuthController
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.ScreenLifecycle
@@ -94,6 +96,7 @@ data class TestMocksModule(
    @get:Provides val demoModeController: DemoModeController = mock(),
    @get:Provides val deviceProvisionedController: DeviceProvisionedController = mock(),
    @get:Provides val dozeParameters: DozeParameters = mock(),
    @get:Provides val dreamManager: DreamManager = mock(),
    @get:Provides val dumpManager: DumpManager = mock(),
    @get:Provides val fingerprintManager: FingerprintManager = mock(),
    @get:Provides val headsUpManager: HeadsUpManager = mock(),
@@ -132,6 +135,7 @@ data class TestMocksModule(
    @get:Provides val systemUIDialogManager: SystemUIDialogManager = mock(),
    @get:Provides val deviceEntryIconTransitions: Set<DeviceEntryIconTransition> = emptySet(),
    @get:Provides val communalInteractor: CommunalInteractor = mock(),
    @get:Provides val communalSceneInteractor: CommunalSceneInteractor = mock(),
    @get:Provides val sceneLogger: SceneLogger = mock(),
    @get:Provides val trustManager: TrustManager = mock(),
    @get:Provides val primaryBouncerInteractor: PrimaryBouncerInteractor = mock(),
Loading