Loading packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt +108 −8 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading Loading @@ -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 } } Loading Loading @@ -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 Loading @@ -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) Loading @@ -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 = Loading @@ -190,7 +195,7 @@ class LockscreenUserActionsViewModelTest : SysuiTestCase() { WakefulnessState.AWAKE } else { WakefulnessState.ASLEEP }, } ) val userActions by collectLastValue(underTest.actions) Loading @@ -212,7 +217,7 @@ class LockscreenUserActionsViewModelTest : SysuiTestCase() { .isEqualTo( expectedDownDestination( downFromEdge = downFromEdge, isSingleShade = isSingleShade, isNarrowScreen = isNarrowScreen, isShadeTouchable = isShadeTouchable, ) ) Loading @@ -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( Loading packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt +17 −4 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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) Loading @@ -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() } } packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModel.kt +49 −51 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModel.kt +30 −33 Original line number Diff line number Diff line Loading @@ -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 Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt +108 −8 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading Loading @@ -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 } } Loading Loading @@ -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 Loading @@ -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) Loading @@ -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 = Loading @@ -190,7 +195,7 @@ class LockscreenUserActionsViewModelTest : SysuiTestCase() { WakefulnessState.AWAKE } else { WakefulnessState.ASLEEP }, } ) val userActions by collectLastValue(underTest.actions) Loading @@ -212,7 +217,7 @@ class LockscreenUserActionsViewModelTest : SysuiTestCase() { .isEqualTo( expectedDownDestination( downFromEdge = downFromEdge, isSingleShade = isSingleShade, isNarrowScreen = isNarrowScreen, isShadeTouchable = isShadeTouchable, ) ) Loading @@ -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( Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt +17 −4 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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) Loading @@ -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() } }
packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModel.kt +49 −51 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading
packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModel.kt +30 −33 Original line number Diff line number Diff line Loading @@ -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 Loading