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

Commit 0df244d3 authored by William Xiao's avatar William Xiao Committed by Android (Google) Code Review
Browse files

Merge "Add dream scene transitions for Flexiglass" into main

parents 8ab15616 8d158660
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