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

Commit 1fcfc067 authored by Danny Burakov's avatar Danny Burakov Committed by Android (Google) Code Review
Browse files

Merge "[bc25] Set overlay shade entry points from Lockscreen and Gone scenes." into main

parents 91b011db 33f83c23
Loading
Loading
Loading
Loading
+108 −8
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@

package com.android.systemui.keyguard.ui.viewmodel

import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
@@ -27,6 +28,7 @@ import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.TransitionKey
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.UserActionResult.ShowOverlay
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -42,8 +44,10 @@ import com.android.systemui.lifecycle.activateIn
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.TransitionKeys
import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.testKosmos
@@ -111,12 +115,12 @@ class LockscreenUserActionsViewModelTest : SysuiTestCase() {

        private fun expectedDownDestination(
            downFromEdge: Boolean,
            isSingleShade: Boolean,
            isNarrowScreen: Boolean,
            isShadeTouchable: Boolean,
        ): SceneKey? {
            return when {
                !isShadeTouchable -> null
                downFromEdge && isSingleShade -> Scenes.QuickSettings
                downFromEdge && isNarrowScreen -> Scenes.QuickSettings
                else -> Scenes.Shade
            }
        }
@@ -162,7 +166,7 @@ class LockscreenUserActionsViewModelTest : SysuiTestCase() {
    @JvmField @Parameter(0) var canSwipeToEnter: Boolean = false
    @JvmField @Parameter(1) var downWithTwoPointers: Boolean = false
    @JvmField @Parameter(2) var downFromEdge: Boolean = false
    @JvmField @Parameter(3) var isSingleShade: Boolean = true
    @JvmField @Parameter(3) var isNarrowScreen: Boolean = true
    @JvmField @Parameter(4) var isCommunalAvailable: Boolean = false
    @JvmField @Parameter(5) var isShadeTouchable: Boolean = false

@@ -170,7 +174,8 @@ class LockscreenUserActionsViewModelTest : SysuiTestCase() {

    @Test
    @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
    fun userActions() =
    @DisableFlags(Flags.FLAG_DUAL_SHADE)
    fun userActions_fullscreenShade() =
        testScope.runTest {
            underTest.activateIn(this)
            kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
@@ -182,7 +187,7 @@ class LockscreenUserActionsViewModelTest : SysuiTestCase() {
                }
            )
            sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
            kosmos.shadeRepository.setShadeLayoutWide(!isSingleShade)
            kosmos.shadeRepository.setShadeLayoutWide(!isNarrowScreen)
            kosmos.setCommunalAvailable(isCommunalAvailable)
            kosmos.fakePowerRepository.updateWakefulness(
                rawState =
@@ -190,7 +195,7 @@ class LockscreenUserActionsViewModelTest : SysuiTestCase() {
                        WakefulnessState.AWAKE
                    } else {
                        WakefulnessState.ASLEEP
                    },
                    }
            )

            val userActions by collectLastValue(underTest.actions)
@@ -212,7 +217,7 @@ class LockscreenUserActionsViewModelTest : SysuiTestCase() {
                .isEqualTo(
                    expectedDownDestination(
                        downFromEdge = downFromEdge,
                        isSingleShade = isSingleShade,
                        isNarrowScreen = isNarrowScreen,
                        isShadeTouchable = isShadeTouchable,
                    )
                )
@@ -220,10 +225,105 @@ class LockscreenUserActionsViewModelTest : SysuiTestCase() {
            assertThat(downDestination?.transitionKey)
                .isEqualTo(
                    expectedDownTransitionKey(
                        isSingleShade = isSingleShade,
                        isSingleShade = isNarrowScreen,
                        isShadeTouchable = isShadeTouchable,
                    )
                )

            val upScene by
                collectLastValue(
                    (userActions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene?.let {
                        scene ->
                        kosmos.sceneInteractor.resolveSceneFamily(scene)
                    } ?: flowOf(null)
                )

            assertThat(upScene)
                .isEqualTo(
                    expectedUpDestination(
                        canSwipeToEnter = canSwipeToEnter,
                        isShadeTouchable = isShadeTouchable,
                    )
                )

            val leftScene by
                collectLastValue(
                    (userActions?.get(Swipe.Left) as? UserActionResult.ChangeScene)?.toScene?.let {
                        scene ->
                        kosmos.sceneInteractor.resolveSceneFamily(scene)
                    } ?: flowOf(null)
                )

            assertThat(leftScene)
                .isEqualTo(
                    expectedLeftDestination(
                        isCommunalAvailable = isCommunalAvailable,
                        isShadeTouchable = isShadeTouchable,
                    )
                )
        }

    @Test
    @EnableFlags(Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_DUAL_SHADE)
    fun userActions_dualShade() =
        testScope.runTest {
            underTest.activateIn(this)
            kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                if (canSwipeToEnter) {
                    AuthenticationMethodModel.None
                } else {
                    AuthenticationMethodModel.Pin
                }
            )
            sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
            kosmos.shadeRepository.setShadeLayoutWide(!isNarrowScreen)
            kosmos.setCommunalAvailable(isCommunalAvailable)
            kosmos.fakePowerRepository.updateWakefulness(
                rawState =
                    if (isShadeTouchable) {
                        WakefulnessState.AWAKE
                    } else {
                        WakefulnessState.ASLEEP
                    }
            )

            val userActions by collectLastValue(underTest.actions)

            val downDestination =
                userActions?.get(
                    Swipe(
                        SwipeDirection.Down,
                        fromSource = Edge.Top.takeIf { downFromEdge },
                        pointerCount = if (downWithTwoPointers) 2 else 1,
                    )
                )

            if (downFromEdge || downWithTwoPointers || !isShadeTouchable) {
                // Top edge is not applicable in dual shade, as well as two-finger swipe.
                assertThat(downDestination).isNull()
            } else {
                assertThat(downDestination).isEqualTo(ShowOverlay(Overlays.NotificationsShade))
                assertThat(downDestination?.transitionKey).isNull()
            }

            val downFromTopRightDestination =
                userActions?.get(
                    Swipe(
                        SwipeDirection.Down,
                        fromSource = SceneContainerEdge.TopRight,
                        pointerCount = if (downWithTwoPointers) 2 else 1,
                    )
                )
            when {
                !isShadeTouchable -> assertThat(downFromTopRightDestination).isNull()
                downWithTwoPointers -> assertThat(downFromTopRightDestination).isNull()
                else -> {
                    assertThat(downFromTopRightDestination)
                        .isEqualTo(ShowOverlay(Overlays.QuickSettingsShade))
                    assertThat(downFromTopRightDestination?.transitionKey).isNull()
                }
            }

            val upScene by
                collectLastValue(
+17 −4
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.systemui.scene.ui.viewmodel

import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -29,6 +31,7 @@ import com.android.systemui.lifecycle.activateIn
import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -52,14 +55,12 @@ class GoneUserActionsViewModelTest : SysuiTestCase() {

    @Before
    fun setUp() {
        underTest =
            GoneUserActionsViewModel(
                shadeInteractor = kosmos.shadeInteractor,
            )
        underTest = GoneUserActionsViewModel(shadeInteractor = kosmos.shadeInteractor)
        underTest.activateIn(testScope)
    }

    @Test
    @DisableFlags(DualShade.FLAG_NAME)
    fun downTransitionKey_splitShadeEnabled_isGoneToSplitShade() =
        testScope.runTest {
            val userActions by collectLastValue(underTest.actions)
@@ -71,12 +72,24 @@ class GoneUserActionsViewModelTest : SysuiTestCase() {
        }

    @Test
    @DisableFlags(DualShade.FLAG_NAME)
    fun downTransitionKey_splitShadeDisabled_isNull() =
        testScope.runTest {
            val userActions by collectLastValue(underTest.actions)
            shadeRepository.setShadeLayoutWide(false)
            runCurrent()

            assertThat(userActions?.get(Swipe(SwipeDirection.Down))?.transitionKey).isNull()
        }

    @Test
    @EnableFlags(DualShade.FLAG_NAME)
    fun downTransitionKey_dualShadeEnabled_isNull() =
        testScope.runTest {
            val userActions by collectLastValue(underTest.actions)
            shadeRepository.setShadeLayoutWide(true)
            runCurrent()

            assertThat(userActions?.get(Swipe(SwipeDirection.Down))?.transitionKey).isNull()
        }
}
+49 −51
Original line number Diff line number Diff line
@@ -21,17 +21,19 @@ package com.android.systemui.keyguard.ui.viewmodel
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.TransitionKey
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge
import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.util.kotlin.filterValuesNotNull
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -52,71 +54,67 @@ constructor(
        shadeInteractor.isShadeTouchable
            .flatMapLatest { isShadeTouchable ->
                if (!isShadeTouchable) {
                    flowOf(emptyMap())
                } else {
                    return@flatMapLatest flowOf(emptyMap())
                }

                combine(
                    deviceEntryInteractor.isUnlocked,
                    communalInteractor.isCommunalAvailable,
                    shadeInteractor.shadeMode,
                ) { isDeviceUnlocked, isCommunalAvailable, shadeMode ->
                        val notifShadeSceneKey =
                            UserActionResult(
                                toScene = SceneFamilies.NotifShade,
                                transitionKey =
                                    ToSplitShade.takeIf { shadeMode is ShadeMode.Split },
                            )

                        mapOf(
                                Swipe.Left to
                                    UserActionResult(Scenes.Communal).takeIf {
                                        isCommunalAvailable
                                    },
                                Swipe.Up to if (isDeviceUnlocked) Scenes.Gone else Scenes.Bouncer,

                                // Swiping down from the top edge goes to QS (or shade if in split
                                // shade mode).
                                swipeDownFromTop(pointerCount = 1) to
                                    if (shadeMode is ShadeMode.Single) {
                                        UserActionResult(Scenes.QuickSettings)
                                    } else {
                                        notifShadeSceneKey
                                    },
                    buildList {
                            if (isCommunalAvailable) {
                                add(Swipe.Left to Scenes.Communal)
                            }

                                // TODO(b/338577208): Remove once we add Dual Shade invocation zones
                                swipeDownFromTop(pointerCount = 2) to
                                    UserActionResult(
                                        toScene = SceneFamilies.QuickSettings,
                                        transitionKey =
                                            ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
                                    ),
                            add(Swipe.Up to if (isDeviceUnlocked) Scenes.Gone else Scenes.Bouncer)

                                // Swiping down, not from the edge, always navigates to the notif
                                // shade scene.
                                swipeDown(pointerCount = 1) to notifShadeSceneKey,
                                swipeDown(pointerCount = 2) to notifShadeSceneKey,
                            addAll(
                                when (shadeMode) {
                                    ShadeMode.Single -> fullscreenShadeActions()
                                    ShadeMode.Split ->
                                        fullscreenShadeActions(transitionKey = ToSplitShade)
                                    ShadeMode.Dual -> dualShadeActions()
                                }
                            )
                            .filterValuesNotNull()
                        }
                        .associate { it }
                }
            }
            .collect { setActions(it) }
    }

    private fun swipeDownFromTop(pointerCount: Int): Swipe {
        return Swipe(
            SwipeDirection.Down,
            fromSource = Edge.Top,
            pointerCount = pointerCount,
    private fun fullscreenShadeActions(
        transitionKey: TransitionKey? = null
    ): Array<Pair<UserAction, UserActionResult>> {
        val notifShadeSceneKey = UserActionResult(SceneFamilies.NotifShade, transitionKey)
        val qsShadeSceneKey = UserActionResult(SceneFamilies.QuickSettings, transitionKey)
        return arrayOf(
            // Swiping down, not from the edge, always goes to shade.
            Swipe.Down to notifShadeSceneKey,
            swipeDown(pointerCount = 2) to notifShadeSceneKey,
            // Swiping down from the top edge goes to QS.
            swipeDownFromTop(pointerCount = 1) to qsShadeSceneKey,
            swipeDownFromTop(pointerCount = 2) to qsShadeSceneKey,
        )
    }

    private fun swipeDown(pointerCount: Int): Swipe {
        return Swipe(
            SwipeDirection.Down,
            pointerCount = pointerCount,
    private fun dualShadeActions(): Array<Pair<UserAction, UserActionResult>> {
        return arrayOf(
            Swipe.Down to Overlays.NotificationsShade,
            Swipe(direction = SwipeDirection.Down, fromSource = SceneContainerEdge.TopRight) to
                Overlays.QuickSettingsShade,
        )
    }

    private fun swipeDownFromTop(pointerCount: Int): Swipe {
        return Swipe(SwipeDirection.Down, fromSource = Edge.Top, pointerCount = pointerCount)
    }

    private fun swipeDown(pointerCount: Int): Swipe {
        return Swipe(SwipeDirection.Down, pointerCount = pointerCount)
    }

    @AssistedFactory
    interface Factory {
        fun create(): LockscreenUserActionsViewModel
+30 −33
Original line number Diff line number Diff line
@@ -19,52 +19,49 @@ package com.android.systemui.scene.ui.viewmodel
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.TransitionKey
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.map

class GoneUserActionsViewModel
@AssistedInject
constructor(
    private val shadeInteractor: ShadeInteractor,
) : UserActionsViewModel() {
constructor(private val shadeInteractor: ShadeInteractor) : UserActionsViewModel() {

    override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
        shadeInteractor.shadeMode
            .map { shadeMode ->
                buildMap<UserAction, UserActionResult> {
                    if (
                        shadeMode is ShadeMode.Single ||
                            // TODO(b/338577208): Remove this once we add Dual Shade invocation
                            // zones.
                            shadeMode is ShadeMode.Dual
                    ) {
                        put(
                            Swipe(
                                pointerCount = 2,
                                fromSource = Edge.Top,
                                direction = SwipeDirection.Down,
                            ),
                            UserActionResult(SceneFamilies.QuickSettings)
        shadeInteractor.shadeMode.collect { shadeMode ->
            setActions(
                when (shadeMode) {
                    ShadeMode.Single -> fullscreenShadeActions()
                    ShadeMode.Split -> fullscreenShadeActions(transitionKey = ToSplitShade)
                    ShadeMode.Dual -> dualShadeActions()
                }
            )
        }
    }

                    put(
                        Swipe.Down,
                        UserActionResult(
                            SceneFamilies.NotifShade,
                            ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
                        )
    private fun fullscreenShadeActions(
        transitionKey: TransitionKey? = null
    ): Map<UserAction, UserActionResult> {
        return mapOf(
            Swipe.Down to UserActionResult(Scenes.Shade, transitionKey),
            Swipe(direction = SwipeDirection.Down, pointerCount = 2, fromSource = Edge.Top) to
                UserActionResult(Scenes.Shade, transitionKey),
        )
    }
            }
            .collect { setActions(it) }

    private fun dualShadeActions(): Map<UserAction, UserActionResult> {
        return mapOf(
            Swipe.Down to Overlays.NotificationsShade,
            Swipe(direction = SwipeDirection.Down, fromSource = SceneContainerEdge.TopRight) to
                Overlays.QuickSettingsShade,
        )
    }

    @AssistedFactory