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

Commit 41ec5cc5 authored by Will Leshner's avatar Will Leshner
Browse files

Transition to hub from screen off when applicable.

This change fixes the dream->off->hub transition so that hub appears
instead of lockscreen on power on.

The original change was reverted due to a performance regression on
devices where the hub doesn't exist. This new change includes a check
that the screen turned on due to a power button press, as well as a
slight refactor to avoid doing unnecessary work when communal hub is
disabled.

Bug: 339315487, 350757214
Flag: com.android.systemui.communal_hub
Test: atest CommunalSceneStartableTest
Test: atest FromDozingTransitionInteractorTest
Test: atest CommunalInteractorTest

Change-Id: I3fe34e57a14af956c0bef8d2fa04042ad01d429e
parent 732fe397
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