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

Commit 8d158660 authored by William Xiao's avatar William Xiao
Browse files

Add dream scene transitions for Flexiglass

Also includes dream to communal hub transitions

Bug: 365999124
Fixed: 365999124
Fixed: 323070866
Fixed: 370069335
Flag: com.android.systemui.scene_container
Test: atest DreamOverlayServiceTest SceneContainerOcclusionInteractorTest SceneContainerStartableTest
Change-Id: If9a2e3934a4995ab4959b9cc2f06305575d45e36
parent 9b4fcde9
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@ import com.android.systemui.scene.ui.composable.transitions.bouncerToLockscreenP
import com.android.systemui.scene.ui.composable.transitions.communalToBouncerTransition
import com.android.systemui.scene.ui.composable.transitions.communalToShadeTransition
import com.android.systemui.scene.ui.composable.transitions.dreamToBouncerTransition
import com.android.systemui.scene.ui.composable.transitions.dreamToCommunalTransition
import com.android.systemui.scene.ui.composable.transitions.dreamToGoneTransition
import com.android.systemui.scene.ui.composable.transitions.dreamToShadeTransition
import com.android.systemui.scene.ui.composable.transitions.goneToQuickSettingsTransition
@@ -58,6 +59,7 @@ val SceneContainerTransitions = transitions {

    from(Scenes.Bouncer, to = Scenes.Gone) { bouncerToGoneTransition() }
    from(Scenes.Dream, to = Scenes.Bouncer) { dreamToBouncerTransition() }
    from(Scenes.Dream, to = Scenes.Communal) { dreamToCommunalTransition() }
    from(Scenes.Dream, to = Scenes.Gone) { dreamToGoneTransition() }
    from(Scenes.Dream, to = Scenes.Shade) { dreamToShadeTransition() }
    from(Scenes.Gone, to = Scenes.Shade) { goneToShadeTransition() }
+33 −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.
 */

package com.android.systemui.scene.ui.composable.transitions

import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
import com.android.systemui.communal.ui.compose.AllElements
import com.android.systemui.communal.ui.compose.Communal

fun TransitionBuilder.dreamToCommunalTransition() {
    spec = tween(durationMillis = 1000)

    // Translate communal hub grid from the end direction.
    translate(Communal.Elements.Grid, Edge.End)

    // Fade all communal hub elements.
    timestampRange(startMillis = 167, endMillis = 334) { fade(AllElements) }
}
+55 −3
Original line number Diff line number Diff line
@@ -19,7 +19,9 @@ import android.app.WindowConfiguration
import android.content.ComponentName
import android.content.Intent
import android.os.RemoteException
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import android.service.dreams.Flags
import android.service.dreams.IDreamOverlay
import android.service.dreams.IDreamOverlayCallback
@@ -33,7 +35,6 @@ import android.view.WindowManagerImpl
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.app.viewcapture.ViewCapture
import com.android.app.viewcapture.ViewCaptureAwareWindowManager
@@ -43,6 +44,7 @@ import com.android.internal.logging.UiEventLogger
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.SysuiTestCase
import com.android.systemui.ambient.touch.TouchMonitor
import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
@@ -62,12 +64,16 @@ import com.android.systemui.complication.ComplicationLayoutEngine
import com.android.systemui.complication.dagger.ComplicationComponent
import com.android.systemui.dreams.complication.HideComplicationTouchHandler
import com.android.systemui.dreams.dagger.DreamOverlayComponent
import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.gesture.domain.gestureInteractor
import com.android.systemui.kosmos.testScope
import com.android.systemui.navigationbar.gestural.domain.GestureInteractor
import com.android.systemui.navigationbar.gestural.domain.TaskInfo
import com.android.systemui.navigationbar.gestural.domain.TaskMatcher
import com.android.systemui.scene.data.repository.sceneContainerRepository
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
import com.android.systemui.touch.TouchInsetManager
import com.android.systemui.util.concurrency.FakeExecutor
@@ -98,12 +104,14 @@ import org.mockito.kotlin.spy
import org.mockito.kotlin.times
import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@RunWith(AndroidJUnit4::class)
class DreamOverlayServiceTest : SysuiTestCase() {
@RunWith(ParameterizedAndroidJunit4::class)
class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() {
    private val mFakeSystemClock = FakeSystemClock()
    private val mMainExecutor = FakeExecutor(mFakeSystemClock)
    private val kosmos = testKosmos()
@@ -245,6 +253,10 @@ class DreamOverlayServiceTest : SysuiTestCase() {
        )
    }

    init {
        mSetFlagsRule.setFlagsParameterization(flags!!)
    }

    @Before
    fun setup() {
        MockitoAnnotations.initMocks(this)
@@ -287,6 +299,7 @@ class DreamOverlayServiceTest : SysuiTestCase() {
                mKeyguardUpdateMonitor,
                mScrimManager,
                mCommunalInteractor,
                kosmos.sceneInteractor,
                mSystemDialogsCloser,
                mUiEventLogger,
                mTouchInsetManager,
@@ -768,6 +781,7 @@ class DreamOverlayServiceTest : SysuiTestCase() {

    @Test
    @EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT, FLAG_COMMUNAL_HUB)
    @DisableFlags(FLAG_SCENE_CONTAINER)
    @kotlin.Throws(RemoteException::class)
    fun testTransitionToGlanceableHub() =
        testScope.runTest {
@@ -792,6 +806,35 @@ class DreamOverlayServiceTest : SysuiTestCase() {
            verify(mUiEventLogger).log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_DREAM_AWAKE_START)
        }

    @Test
    @EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT, FLAG_SCENE_CONTAINER, FLAG_COMMUNAL_HUB)
    @kotlin.Throws(RemoteException::class)
    fun testTransitionToGlanceableHub_sceneContainer() =
        testScope.runTest {
            // Inform the overlay service of dream starting. Do not show dream complications.
            client.startDream(
                mWindowParams,
                mDreamOverlayCallback,
                DREAM_COMPONENT,
                false /*isPreview*/,
                false, /*shouldShowComplication*/
            )
            mMainExecutor.runAllReady()

            verify(mDreamOverlayCallback).onRedirectWake(false)
            clearInvocations(mDreamOverlayCallback)
            kosmos.setCommunalAvailable(true)
            mMainExecutor.runAllReady()
            runCurrent()
            verify(mDreamOverlayCallback).onRedirectWake(true)
            client.onWakeRequested()
            mMainExecutor.runAllReady()
            runCurrent()
            assertThat(kosmos.sceneContainerRepository.currentScene.value)
                .isEqualTo(Scenes.Communal)
            verify(mUiEventLogger).log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_DREAM_AWAKE_START)
        }

    @Test
    @EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT, FLAG_COMMUNAL_HUB)
    @Throws(RemoteException::class)
@@ -911,6 +954,7 @@ class DreamOverlayServiceTest : SysuiTestCase() {
    // Verifies that the touch handling lifecycle is STARTED even if the dream starts while not
    // focused.
    @Test
    @DisableFlags(FLAG_SCENE_CONTAINER)
    fun testLifecycle_dreamNotFocusedOnStart_isStarted() {
        val transitionState: MutableStateFlow<ObservableTransitionState> =
            MutableStateFlow(ObservableTransitionState.Idle(CommunalScenes.Blank))
@@ -1024,6 +1068,7 @@ class DreamOverlayServiceTest : SysuiTestCase() {
    }

    @Test
    @DisableFlags(FLAG_SCENE_CONTAINER)
    fun testCommunalVisible_setsLifecycleState() {
        val client = client

@@ -1060,6 +1105,7 @@ class DreamOverlayServiceTest : SysuiTestCase() {

    // Verifies the dream's lifecycle
    @Test
    @DisableFlags(FLAG_SCENE_CONTAINER)
    fun testLifecycleStarted_whenAnyOcclusion() {
        val client = client

@@ -1256,5 +1302,11 @@ class DreamOverlayServiceTest : SysuiTestCase() {
            ComponentName("package", "homeControlPanel")
        private const val DREAM_COMPONENT = "package/dream"
        private const val WINDOW_NAME = "test"

        @JvmStatic
        @Parameters(name = "{0}")
        fun getParams(): List<FlagsParameterization> {
            return FlagsParameterization.allCombinationsOf(FLAG_COMMUNAL_HUB).andSceneContainer()
        }
    }
}
+19 −0
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import com.android.compose.animation.scene.ObservableTransitionState.Transition.
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.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
@@ -194,6 +195,24 @@ class SceneContainerOcclusionInteractorTest : SysuiTestCase() {
                .isFalse()
        }

    @Test
    fun invisibleDueToOcclusion_isDreaming_emitsTrue() =
        testScope.runTest {
            val invisibleDueToOcclusion by collectLastValue(underTest.invisibleDueToOcclusion)

            // Verify that we start with unoccluded
            assertWithMessage("Should start unoccluded").that(invisibleDueToOcclusion).isFalse()

            // Start dreaming, which is an occluding activity
            showOccludingActivity()
            kosmos.keyguardInteractor.setDreaming(true)

            // Verify not invisible when dreaming
            assertWithMessage("Should be invisible when dreaming")
                .that(invisibleDueToOcclusion)
                .isTrue()
        }

    /** Simulates the appearance of a show-when-locked `Activity` in the foreground. */
    private fun TestScope.showOccludingActivity() {
        keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(
+123 −2
Original line number Diff line number Diff line
@@ -73,6 +73,7 @@ import com.android.systemui.keyguard.data.repository.fakeTrustRepository
import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.keyguard.dismissCallbackRegistry
import com.android.systemui.keyguard.domain.interactor.dozeInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.scenetransition.lockscreenSceneTransitionInteractor
@@ -143,6 +144,8 @@ class SceneContainerStartableTest : SysuiTestCase() {
    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
    private val deviceEntryHapticsInteractor by lazy { kosmos.deviceEntryHapticsInteractor }
    private val dozeInteractor by lazy { kosmos.dozeInteractor }
    private val keyguardInteractor by lazy { kosmos.keyguardInteractor }
    private val sceneInteractor by lazy { kosmos.sceneInteractor }
    private val sceneBackInteractor by lazy { kosmos.sceneBackInteractor }
    private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
@@ -372,6 +375,64 @@ class SceneContainerStartableTest : SysuiTestCase() {
            assertThat(isVisible).isTrue()
        }

    @Test
    fun hydrateVisibility_whileDreaming() =
        testScope.runTest {
            val isVisible by collectLastValue(sceneInteractor.isVisible)

            // GIVEN the device is dreaming
            val transitionState =
                prepareState(isDeviceUnlocked = false, initialSceneKey = Scenes.Dream)
            underTest.start()
            assertThat(isVisible).isFalse()
        }

    @Test
    fun hydrateVisibility_onCommunalWhileOccluded() =
        testScope.runTest {
            val isVisible by collectLastValue(sceneInteractor.isVisible)

            kosmos.keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(
                true,
                mock(),
            )
            prepareState(isDeviceUnlocked = false, initialSceneKey = Scenes.Communal)
            underTest.start()
            runCurrent()
            assertThat(isVisible).isTrue()
        }

    @Test
    fun hydrateVisibility_inCommunalTransition() =
        testScope.runTest {
            val isVisible by collectLastValue(sceneInteractor.isVisible)

            // GIVEN the device is dreaming
            val transitionState =
                prepareState(
                    authenticationMethod = AuthenticationMethodModel.Pin,
                    isDeviceUnlocked = false,
                    initialSceneKey = Scenes.Dream,
                )
            underTest.start()
            assertThat(isVisible).isFalse()

            // WHEN a transition starts to the communal hub
            sceneInteractor.changeScene(Scenes.Dream, "switching to dream for test")
            transitionState.value =
                ObservableTransitionState.Transition(
                    fromScene = Scenes.Dream,
                    toScene = Scenes.Communal,
                    currentScene = flowOf(Scenes.Dream),
                    progress = flowOf(0.5f),
                    isInitiatedByUserInput = true,
                    isUserInputOngoing = flowOf(false),
                )
            runCurrent()
            // THEN scenes are visible
            assertThat(isVisible).isTrue()
        }

    @Test
    fun startsInLockscreenScene() =
        testScope.runTest {
@@ -643,7 +704,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
    fun switchToAOD_whenAvailable_whenDeviceSleepsLocked() =
        testScope.runTest {
            kosmos.lockscreenSceneTransitionInteractor.start()
            val asleepState by collectLastValue(kosmos.keyguardInteractor.asleepKeyguardState)
            val asleepState by collectLastValue(keyguardInteractor.asleepKeyguardState)
            val currentTransitionInfo by
                collectLastValue(kosmos.keyguardTransitionRepository.currentTransitionInfoInternal)
            val transitionState =
@@ -673,7 +734,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
    fun switchToDozing_whenAodUnavailable_whenDeviceSleepsLocked() =
        testScope.runTest {
            kosmos.lockscreenSceneTransitionInteractor.start()
            val asleepState by collectLastValue(kosmos.keyguardInteractor.asleepKeyguardState)
            val asleepState by collectLastValue(keyguardInteractor.asleepKeyguardState)
            val currentTransitionInfo by
                collectLastValue(kosmos.keyguardTransitionRepository.currentTransitionInfoInternal)
            val transitionState =
@@ -2359,6 +2420,66 @@ class SceneContainerStartableTest : SysuiTestCase() {
            assertThat(isLockscreenEnabled).isTrue()
        }

    @Test
    fun stayOnLockscreen_whenDozingStarted() =
        testScope.runTest {
            val currentScene by collectLastValue(sceneInteractor.currentScene)
            prepareState()
            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
            underTest.start()

            // Stay on Lockscreen when dozing and dreaming
            dozeInteractor.setIsDozing(true)
            keyguardInteractor.setDreaming(true)
            kosmos.fakeKeyguardRepository.setDreamingWithOverlay(false)
            runCurrent()
            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
        }

    @Test
    fun switchFromLockscreenToDream_whenDreamStarted() =
        testScope.runTest {
            val currentScene by collectLastValue(sceneInteractor.currentScene)
            prepareState()
            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
            underTest.start()

            powerInteractor.setAwakeForTest()
            keyguardInteractor.setDreaming(true)
            // Move past initial delay with [KeyguardInteractor#isAbleToDream]
            advanceTimeBy(600L)
            runCurrent()
            assertThat(currentScene).isEqualTo(Scenes.Dream)
        }

    @Test
    fun switchFromDreamToLockscreen_whenLockedAndDreamStopped() =
        testScope.runTest {
            keyguardInteractor.setDreaming(true)
            val currentScene by collectLastValue(sceneInteractor.currentScene)
            prepareState(initialSceneKey = Scenes.Dream)
            assertThat(currentScene).isEqualTo(Scenes.Dream)
            underTest.start()

            keyguardInteractor.setDreaming(false)
            runCurrent()
            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
        }

    @Test
    fun switchFromDreamToGone_whenUnlockedAndDreamStopped() =
        testScope.runTest {
            keyguardInteractor.setDreaming(true)
            val currentScene by collectLastValue(sceneInteractor.currentScene)
            prepareState(initialSceneKey = Scenes.Dream, isDeviceUnlocked = true)
            assertThat(currentScene).isEqualTo(Scenes.Dream)
            underTest.start()

            keyguardInteractor.setDreaming(false)
            runCurrent()
            assertThat(currentScene).isEqualTo(Scenes.Gone)
        }

    @Test
    fun replacesLockscreenSceneOnBackStack_whenUnlockdViaAlternateBouncer_fromShade() =
        testScope.runTest {
Loading