Merge "[Flexiglass] Fixes down swipe destinations to match old impl." into main
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index 96520b2..7acb4d5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -19,61 +19,31 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import com.android.compose.animation.scene.Edge
-import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneScope
-import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.animateSceneFloatAsState
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
import dagger.Lazy
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.stateIn
/** The lock screen scene shows when the device is locked. */
@SysUISingleton
class LockscreenScene
@Inject
constructor(
- @Application private val applicationScope: CoroutineScope,
viewModel: LockscreenSceneViewModel,
private val lockscreenContent: Lazy<LockscreenContent>,
) : ComposableScene {
override val key = Scenes.Lockscreen
override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
- combine(
- viewModel.upDestinationSceneKey,
- viewModel.leftDestinationSceneKey,
- viewModel.downFromTopEdgeDestinationSceneKey,
- ) { upKey, leftKey, downFromTopEdgeKey ->
- destinationScenes(
- up = upKey,
- left = leftKey,
- downFromTopEdge = downFromTopEdgeKey,
- )
- }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue =
- destinationScenes(
- up = viewModel.upDestinationSceneKey.value,
- left = viewModel.leftDestinationSceneKey.value,
- downFromTopEdge = viewModel.downFromTopEdgeDestinationSceneKey.value,
- )
- )
+ viewModel.destinationScenes
@Composable
override fun SceneScope.Content(
@@ -84,22 +54,6 @@
modifier = modifier,
)
}
-
- private fun destinationScenes(
- up: SceneKey?,
- left: SceneKey?,
- downFromTopEdge: SceneKey?,
- ): Map<UserAction, UserActionResult> {
- return buildMap {
- up?.let { this[Swipe(SwipeDirection.Up)] = UserActionResult(up) }
- left?.let { this[Swipe(SwipeDirection.Left)] = UserActionResult(left) }
- downFromTopEdge?.let {
- this[Swipe(fromSource = Edge.Top, direction = SwipeDirection.Down)] =
- UserActionResult(downFromTopEdge)
- }
- this[Swipe(direction = SwipeDirection.Down)] = UserActionResult(Scenes.Shade)
- }
- }
}
@Composable
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 19950a5..2fd2ef1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -19,9 +19,12 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.platform.test.annotations.EnableFlags
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
@@ -31,86 +34,129 @@
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.kosmos.testScope
-import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.shade.domain.startable.shadeStartable
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
+import platform.test.runner.parameterized.Parameter
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(ParameterizedAndroidJunit4::class)
class LockscreenSceneViewModelTest : SysuiTestCase() {
+ companion object {
+ @Parameters(
+ name =
+ "canSwipeToEnter={0}, downWithTwoPointers={1}, downFromEdge={2}," +
+ " isSingleShade={3}, isCommunalAvailable={4}"
+ )
+ @JvmStatic
+ fun combinations() = buildList {
+ repeat(32) { combination ->
+ add(
+ arrayOf(
+ /* canSwipeToEnter= */ combination and 1 != 0,
+ /* downWithTwoPointers= */ combination and 2 != 0,
+ /* downFromEdge= */ combination and 4 != 0,
+ /* isSingleShade= */ combination and 8 != 0,
+ /* isCommunalAvailable= */ combination and 16 != 0,
+ )
+ )
+ }
+ }
+
+ @JvmStatic
+ @BeforeClass
+ fun setUp() {
+ val combinationStrings =
+ combinations().map { array ->
+ check(array.size == 5)
+ "${array[4]},${array[3]},${array[2]},${array[1]},${array[0]}"
+ }
+ val uniqueCombinations = combinationStrings.toSet()
+ assertThat(combinationStrings).hasSize(uniqueCombinations.size)
+ }
+
+ private fun expectedDownDestination(
+ downFromEdge: Boolean,
+ isSingleShade: Boolean,
+ ): SceneKey {
+ return if (downFromEdge && isSingleShade) Scenes.QuickSettings else Scenes.Shade
+ }
+ }
+
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val sceneInteractor by lazy { kosmos.sceneInteractor }
+ @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(4) var isCommunalAvailable: Boolean = false
+
private val underTest by lazy { createLockscreenSceneViewModel() }
@Test
- fun upTransitionSceneKey_canSwipeToUnlock_gone() =
+ @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
+ fun destinationScenes() =
testScope.runTest {
- val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.None
- )
kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
- sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
-
- assertThat(upTransitionSceneKey).isEqualTo(Scenes.Gone)
- }
-
- @Test
- fun upTransitionSceneKey_cannotSwipeToUnlock_bouncer() =
- testScope.runTest {
- val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin
+ if (canSwipeToEnter) {
+ AuthenticationMethodModel.None
+ } else {
+ AuthenticationMethodModel.Pin
+ }
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(canSwipeToEnter)
sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
+ kosmos.shadeRepository.setShadeMode(
+ if (isSingleShade) {
+ ShadeMode.Single
+ } else {
+ ShadeMode.Split
+ }
+ )
+ kosmos.setCommunalAvailable(isCommunalAvailable)
- assertThat(upTransitionSceneKey).isEqualTo(Scenes.Bouncer)
- }
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
- @EnableFlags(FLAG_COMMUNAL_HUB)
- @Test
- fun leftTransitionSceneKey_communalIsAvailable_communal() =
- testScope.runTest {
- val leftDestinationSceneKey by collectLastValue(underTest.leftDestinationSceneKey)
- assertThat(leftDestinationSceneKey).isNull()
+ assertThat(
+ destinationScenes
+ ?.get(
+ Swipe(
+ SwipeDirection.Down,
+ fromSource = Edge.Top.takeIf { downFromEdge },
+ pointerCount = if (downWithTwoPointers) 2 else 1,
+ )
+ )
+ ?.toScene
+ )
+ .isEqualTo(
+ expectedDownDestination(
+ downFromEdge = downFromEdge,
+ isSingleShade = isSingleShade,
+ )
+ )
- kosmos.setCommunalAvailable(true)
- runCurrent()
- assertThat(leftDestinationSceneKey).isEqualTo(Scenes.Communal)
- }
+ assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
+ .isEqualTo(if (canSwipeToEnter) Scenes.Gone else Scenes.Bouncer)
- @Test
- fun downFromTopEdgeDestinationSceneKey_whenNotSplitShade_quickSettings() =
- testScope.runTest {
- overrideResource(R.bool.config_use_split_notification_shade, false)
- kosmos.shadeStartable.start()
- val sceneKey by collectLastValue(underTest.downFromTopEdgeDestinationSceneKey)
- assertThat(sceneKey).isEqualTo(Scenes.QuickSettings)
- }
-
- @Test
- fun downFromTopEdgeDestinationSceneKey_whenSplitShade_null() =
- testScope.runTest {
- overrideResource(R.bool.config_use_split_notification_shade, true)
- kosmos.shadeStartable.start()
- val sceneKey by collectLastValue(underTest.downFromTopEdgeDestinationSceneKey)
- assertThat(sceneKey).isNull()
+ assertThat(destinationScenes?.get(Swipe(SwipeDirection.Left))?.toScene)
+ .isEqualTo(Scenes.Communal.takeIf { isCommunalAvailable })
}
private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 42c3354..af9abcd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -26,7 +26,6 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
import com.android.internal.R
import com.android.internal.util.EmergencyAffordanceManager
import com.android.internal.util.emergencyAffordanceManager
@@ -317,8 +316,8 @@
@Test
fun swipeUpOnLockscreen_enterCorrectPin_unlocksDevice() =
testScope.runTest {
- val upDestinationSceneKey by
- collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
emulateUserDrivenTransition(
to = upDestinationSceneKey,
@@ -337,8 +336,8 @@
testScope.runTest {
setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
- val upDestinationSceneKey by
- collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
emulateUserDrivenTransition(
to = upDestinationSceneKey,
@@ -356,7 +355,7 @@
emulateUserDrivenTransition(to = Scenes.Shade)
assertCurrentScene(Scenes.Shade)
- val upDestinationSceneKey = destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Lockscreen)
emulateUserDrivenTransition(
to = upDestinationSceneKey,
@@ -379,7 +378,7 @@
emulateUserDrivenTransition(to = Scenes.Shade)
assertCurrentScene(Scenes.Shade)
- val upDestinationSceneKey = destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
emulateUserDrivenTransition(
to = upDestinationSceneKey,
@@ -447,8 +446,8 @@
fun swipeUpOnLockscreenWhileUnlocked_dismissesLockscreen() =
testScope.runTest {
unlockDevice()
- val upDestinationSceneKey by
- collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
}
@@ -469,8 +468,8 @@
fun dismissingIme_whileOnPasswordBouncer_navigatesToLockscreen() =
testScope.runTest {
setAuthMethod(AuthenticationMethodModel.Password)
- val upDestinationSceneKey by
- collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
emulateUserDrivenTransition(
to = upDestinationSceneKey,
@@ -487,8 +486,8 @@
fun bouncerActionButtonClick_opensEmergencyServicesDialer() =
testScope.runTest {
setAuthMethod(AuthenticationMethodModel.Password)
- val upDestinationSceneKey by
- collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
emulateUserDrivenTransition(to = upDestinationSceneKey)
@@ -507,8 +506,8 @@
testScope.runTest {
setAuthMethod(AuthenticationMethodModel.Password)
startPhoneCall()
- val upDestinationSceneKey by
- collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
emulateUserDrivenTransition(to = upDestinationSceneKey)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index 288ef3c..993e81b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -14,9 +14,15 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.compose.animation.scene.SceneKey
+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.UserAction
+import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -27,9 +33,10 @@
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
/** Models UI state and handles user input for the lockscreen scene. */
@@ -44,37 +51,69 @@
val longPress: KeyguardLongPressViewModel,
val notifications: NotificationsPlaceholderViewModel,
) {
- /** The key of the scene we should switch to when swiping up. */
- val upDestinationSceneKey: StateFlow<SceneKey> =
- deviceEntryInteractor.isUnlocked
- .map { isUnlocked -> upDestinationSceneKey(isUnlocked) }
+ val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ combine(
+ deviceEntryInteractor.isUnlocked,
+ communalInteractor.isCommunalAvailable,
+ shadeInteractor.shadeMode,
+ ) { isDeviceUnlocked, isCommunalAvailable, shadeMode ->
+ destinationScenes(
+ isDeviceUnlocked = isDeviceUnlocked,
+ isCommunalAvailable = isCommunalAvailable,
+ shadeMode = shadeMode,
+ )
+ }
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = upDestinationSceneKey(deviceEntryInteractor.isUnlocked.value),
+ initialValue =
+ destinationScenes(
+ isDeviceUnlocked = deviceEntryInteractor.isUnlocked.value,
+ isCommunalAvailable = false,
+ shadeMode = shadeInteractor.shadeMode.value,
+ ),
)
- private fun upDestinationSceneKey(isUnlocked: Boolean): SceneKey {
- return if (isUnlocked) Scenes.Gone else Scenes.Bouncer
+ private fun destinationScenes(
+ isDeviceUnlocked: Boolean,
+ isCommunalAvailable: Boolean,
+ shadeMode: ShadeMode,
+ ): Map<UserAction, UserActionResult> {
+ val quickSettingsIfSingleShade =
+ if (shadeMode is ShadeMode.Single) {
+ Scenes.QuickSettings
+ } else {
+ Scenes.Shade
+ }
+
+ return 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 quickSettingsIfSingleShade,
+ swipeDownFromTop(pointerCount = 2) to quickSettingsIfSingleShade,
+
+ // Swiping down, not from the edge, always navigates to the shade scene.
+ swipeDown(pointerCount = 1) to Scenes.Shade,
+ swipeDown(pointerCount = 2) to Scenes.Shade,
+ )
+ .filterValues { it != null }
+ .mapValues { checkNotNull(it.value) }
}
- /** The key of the scene we should switch to when swiping left. */
- val leftDestinationSceneKey: StateFlow<SceneKey?> =
- communalInteractor.isCommunalAvailable
- .map { available -> if (available) Scenes.Communal else null }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = null,
- )
+ private fun swipeDownFromTop(pointerCount: Int): Swipe {
+ return Swipe(
+ SwipeDirection.Down,
+ fromSource = Edge.Top,
+ pointerCount = pointerCount,
+ )
+ }
- /** The key of the scene we should switch to when swiping down from the top edge. */
- val downFromTopEdgeDestinationSceneKey: StateFlow<SceneKey?> =
- shadeInteractor.shadeMode
- .map { shadeMode -> Scenes.QuickSettings.takeIf { shadeMode is ShadeMode.Single } }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = null,
- )
+ private fun swipeDown(pointerCount: Int): Swipe {
+ return Swipe(
+ SwipeDirection.Down,
+ pointerCount = pointerCount,
+ )
+ }
}