diff options
| author | 2024-09-26 20:06:49 -0700 | |
|---|---|---|
| committer | 2024-09-27 16:42:33 -0700 | |
| commit | 2bd406ba9e66bcc4ed1ac3c4ad7408aa37d0c35d (patch) | |
| tree | eef7fd20f5d165caf045476e3a3ae353acb44c13 | |
| parent | 8f2be71b4a3d2ad516339dbfbb6461ea52e58f1b (diff) | |
[flexiglass] Communal scene navigates to shade and bouncer.
Defines navigation user actions between the communal scene and the shade
scene as well as the bouncer scene. Also adds navigation back to Gone if
the user swipes right or up when the device is unlocked.
Still missing:
- The transition animations are missing and will be added in the next CL
- The shade scene returns to lockscreen instead of communal
Bug: 323068793
Bug: 339449476
Test: manually verified navigation. Swipe up for bouncer and swipe
right for lockscreen - when locked. Could not test when unlocked as I
couldn't figure out a way to unlock while on GH. Tested that swipe down goes to shade when locked and
when unlocked.
Test: added unit test for new user action view-model
Flag: com.android.systemui.scene_container
Change-Id: I2966348858911eb69ef7fbb0d81bb90c11f4360b
7 files changed, 438 insertions, 97 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt index e41a7df39c21..a88ad946d95b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt @@ -21,11 +21,10 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle 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.systemui.communal.shared.model.CommunalBackgroundType +import com.android.systemui.communal.ui.viewmodel.CommunalUserActionsViewModel import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.communal.util.CommunalColors import com.android.systemui.dagger.SysUISingleton @@ -33,38 +32,32 @@ import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.composable.Scene import javax.inject.Inject -import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow /** The communal scene shows glanceable hub when the device is locked and docked. */ @SysUISingleton class CommunalScene @Inject constructor( - private val viewModel: CommunalViewModel, + private val contentViewModel: CommunalViewModel, + actionsViewModelFactory: CommunalUserActionsViewModel.Factory, private val communalColors: CommunalColors, private val communalContent: CommunalContent, ) : ExclusiveActivatable(), Scene { override val key = Scenes.Communal - override val userActions: Flow<Map<UserAction, UserActionResult>> = - MutableStateFlow( - mapOf( - Swipe(SwipeDirection.End) to Scenes.Lockscreen, - ) - ) - .asStateFlow() + private val actionsViewModel: CommunalUserActionsViewModel = actionsViewModelFactory.create() + + override val userActions: Flow<Map<UserAction, UserActionResult>> = actionsViewModel.actions override suspend fun onActivated(): Nothing { - awaitCancellation() + actionsViewModel.activate() } @Composable override fun SceneScope.Content(modifier: Modifier) { val backgroundType by - viewModel.communalBackground.collectAsStateWithLifecycle( + contentViewModel.communalBackground.collectAsStateWithLifecycle( initialValue = CommunalBackgroundType.ANIMATED ) @@ -72,7 +65,7 @@ constructor( backgroundType = backgroundType, colors = communalColors, content = communalContent, - viewModel = viewModel, + viewModel = contentViewModel, modifier = modifier.horizontalNestedScrollToScene(), ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt new file mode 100644 index 000000000000..58b59ffd8894 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt @@ -0,0 +1,223 @@ +/* + * 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. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.communal.ui.viewmodel + +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.Swipe +import com.android.compose.animation.scene.UserActionResult +import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor +import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus +import com.android.systemui.kosmos.testScope +import com.android.systemui.lifecycle.activateIn +import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest +import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest +import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.scene.domain.interactor.sceneInteractor +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.shade.data.repository.fakeShadeRepository +import com.android.systemui.shade.shared.flag.DualShade +import com.android.systemui.shade.shared.model.ShadeMode +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +@EnableSceneContainer +class CommunalUserActionsViewModelTest : SysuiTestCase() { + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + + private lateinit var underTest: CommunalUserActionsViewModel + + @Before + fun setUp() { + underTest = kosmos.communalUserActionsViewModel + underTest.activateIn(testScope) + } + + @Test + @DisableFlags(DualShade.FLAG_NAME) + fun actions_singleShade() = + testScope.runTest { + val actions by collectLastValue(underTest.actions) + + setUpState( + isShadeTouchable = true, + isDeviceUnlocked = false, + shadeMode = ShadeMode.Single, + ) + assertThat(actions).isNotEmpty() + assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home)) + assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer)) + assertThat(actions?.get(Swipe.Down)).isEqualTo(UserActionResult(Scenes.Shade)) + + setUpState( + isShadeTouchable = false, + isDeviceUnlocked = false, + shadeMode = ShadeMode.Single, + ) + assertThat(actions).isEmpty() + + setUpState( + isShadeTouchable = true, + isDeviceUnlocked = true, + shadeMode = ShadeMode.Single, + ) + assertThat(actions).isNotEmpty() + assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home)) + assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone)) + assertThat(actions?.get(Swipe.Down)).isEqualTo(UserActionResult(Scenes.Shade)) + } + + @Test + @DisableFlags(DualShade.FLAG_NAME) + fun actions_splitShade() = + testScope.runTest { + val actions by collectLastValue(underTest.actions) + + setUpState( + isShadeTouchable = true, + isDeviceUnlocked = false, + shadeMode = ShadeMode.Split, + ) + assertThat(actions).isNotEmpty() + assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home)) + assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer)) + assertThat(actions?.get(Swipe.Down)) + .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade)) + + setUpState( + isShadeTouchable = false, + isDeviceUnlocked = false, + shadeMode = ShadeMode.Split, + ) + assertThat(actions).isEmpty() + + setUpState( + isShadeTouchable = true, + isDeviceUnlocked = true, + shadeMode = ShadeMode.Split, + ) + assertThat(actions).isNotEmpty() + assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home)) + assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone)) + assertThat(actions?.get(Swipe.Down)) + .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade)) + } + + @Test + @EnableFlags(DualShade.FLAG_NAME) + fun actions_dualShade() = + testScope.runTest { + val actions by collectLastValue(underTest.actions) + + setUpState( + isShadeTouchable = true, + isDeviceUnlocked = false, + shadeMode = ShadeMode.Dual, + ) + assertThat(actions).isNotEmpty() + assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home)) + assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer)) + assertThat(actions?.get(Swipe.Down)) + .isEqualTo(UserActionResult(Overlays.NotificationsShade)) + + setUpState( + isShadeTouchable = false, + isDeviceUnlocked = false, + shadeMode = ShadeMode.Dual, + ) + assertThat(actions).isEmpty() + + setUpState(isShadeTouchable = true, isDeviceUnlocked = true, shadeMode = ShadeMode.Dual) + assertThat(actions).isNotEmpty() + assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home)) + assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone)) + assertThat(actions?.get(Swipe.Down)) + .isEqualTo(UserActionResult(Overlays.NotificationsShade)) + } + + private fun TestScope.setUpState( + isShadeTouchable: Boolean, + isDeviceUnlocked: Boolean, + shadeMode: ShadeMode, + ) { + if (isShadeTouchable) { + kosmos.powerInteractor.setAwakeForTest() + } else { + kosmos.powerInteractor.setAsleepForTest() + } + + if (isDeviceUnlocked) { + unlockDevice() + } else { + lockDevice() + } + + if (shadeMode == ShadeMode.Dual) { + assertThat(DualShade.isEnabled).isTrue() + } else { + assertThat(DualShade.isEnabled).isFalse() + kosmos.fakeShadeRepository.setShadeLayoutWide(shadeMode == ShadeMode.Split) + } + runCurrent() + } + + private fun TestScope.lockDevice() { + val deviceUnlockStatus by + collectLastValue(kosmos.deviceUnlockedInteractor.deviceUnlockStatus) + + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + assertThat(deviceUnlockStatus?.isUnlocked).isFalse() + kosmos.sceneInteractor.changeScene(Scenes.Lockscreen, "reason") + runCurrent() + } + + private fun TestScope.unlockDevice() { + val deviceUnlockStatus by + collectLastValue(kosmos.deviceUnlockedInteractor.deviceUnlockStatus) + + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + assertThat(deviceUnlockStatus?.isUnlocked).isTrue() + kosmos.sceneInteractor.changeScene(Scenes.Gone, "reason") + runCurrent() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModel.kt new file mode 100644 index 000000000000..e35fdfe9087c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModel.kt @@ -0,0 +1,83 @@ +/* + * 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.communal.ui.viewmodel + +import com.android.compose.animation.scene.Swipe +import com.android.compose.animation.scene.UserAction +import com.android.compose.animation.scene.UserActionResult +import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor +import com.android.systemui.scene.shared.model.SceneFamilies +import com.android.systemui.scene.shared.model.Scenes +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.shade.ui.viewmodel.dualShadeActions +import com.android.systemui.shade.ui.viewmodel.singleShadeActions +import com.android.systemui.shade.ui.viewmodel.splitShadeActions +import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map + +/** Provides scene container user actions and results. */ +class CommunalUserActionsViewModel +@AssistedInject +constructor( + private val deviceUnlockedInteractor: DeviceUnlockedInteractor, + private val shadeInteractor: ShadeInteractor, +) : UserActionsViewModel() { + + override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) { + shadeInteractor.isShadeTouchable + .flatMapLatestConflated { isShadeTouchable -> + if (!isShadeTouchable) { + flowOf(emptyMap()) + } else { + combine( + deviceUnlockedInteractor.deviceUnlockStatus.map { it.isUnlocked }, + shadeInteractor.shadeMode, + ) { isDeviceUnlocked, shadeMode -> + buildList { + val bouncerOrGone = + if (isDeviceUnlocked) Scenes.Gone else Scenes.Bouncer + add(Swipe.Up to bouncerOrGone) + + // "Home" is either Lockscreen, or Gone - if the device is entered. + add(Swipe.End to SceneFamilies.Home) + + addAll( + when (shadeMode) { + ShadeMode.Single -> singleShadeActions() + ShadeMode.Split -> splitShadeActions() + ShadeMode.Dual -> dualShadeActions() + } + ) + } + .associate { it } + } + } + } + .collect { setActions(it) } + } + + @AssistedFactory + interface Factory { + fun create(): CommunalUserActionsViewModel + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModel.kt index 3b266f945aab..6d420213a960 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModel.kt @@ -18,20 +18,18 @@ 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.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.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.shade.ui.viewmodel.dualShadeActions +import com.android.systemui.shade.ui.viewmodel.singleShadeActions +import com.android.systemui.shade.ui.viewmodel.splitShadeActions import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -81,45 +79,6 @@ constructor( .collect { setActions(it) } } - private fun singleShadeActions(): Array<Pair<UserAction, UserActionResult>> { - return arrayOf( - // Swiping down, not from the edge, always goes to shade. - Swipe.Down to Scenes.Shade, - swipeDown(pointerCount = 2) to Scenes.Shade, - // Swiping down from the top edge goes to QS. - swipeDownFromTop(pointerCount = 1) to Scenes.QuickSettings, - swipeDownFromTop(pointerCount = 2) to Scenes.QuickSettings, - ) - } - - private fun splitShadeActions(): Array<Pair<UserAction, UserActionResult>> { - val splitShadeSceneKey = UserActionResult(Scenes.Shade, ToSplitShade) - return arrayOf( - // Swiping down, not from the edge, always goes to shade. - Swipe.Down to splitShadeSceneKey, - swipeDown(pointerCount = 2) to splitShadeSceneKey, - // Swiping down from the top edge goes to QS. - swipeDownFromTop(pointerCount = 1) to splitShadeSceneKey, - swipeDownFromTop(pointerCount = 2) to splitShadeSceneKey, - ) - } - - 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 diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModel.kt index 5ff507a45d2e..fc172e8ca1d8 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModel.kt @@ -16,16 +16,13 @@ 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.UserAction import com.android.compose.animation.scene.UserActionResult -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 com.android.systemui.shade.ui.viewmodel.dualShadeActions +import com.android.systemui.shade.ui.viewmodel.singleShadeActions +import com.android.systemui.shade.ui.viewmodel.splitShadeActions import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -36,41 +33,21 @@ constructor(private val shadeInteractor: ShadeInteractor) : UserActionsViewModel override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) { shadeInteractor.shadeMode.collect { shadeMode -> setActions( - when (shadeMode) { - ShadeMode.Single -> singleShadeActions() - ShadeMode.Split -> splitShadeActions() - ShadeMode.Dual -> dualShadeActions() - } + buildList { + addAll( + when (shadeMode) { + ShadeMode.Single -> + singleShadeActions(requireTwoPointersForTopEdgeForQs = true) + ShadeMode.Split -> splitShadeActions() + ShadeMode.Dual -> dualShadeActions() + } + ) + } + .associate { it } ) } } - private fun singleShadeActions(): Map<UserAction, UserActionResult> { - return mapOf( - Swipe.Down to Scenes.Shade, - swipeDownFromTopWithTwoFingers() to Scenes.QuickSettings, - ) - } - - private fun splitShadeActions(): Map<UserAction, UserActionResult> { - return mapOf( - Swipe.Down to UserActionResult(Scenes.Shade, ToSplitShade), - swipeDownFromTopWithTwoFingers() to UserActionResult(Scenes.Shade, ToSplitShade), - ) - } - - private fun dualShadeActions(): Map<UserAction, UserActionResult> { - return mapOf( - Swipe.Down to Overlays.NotificationsShade, - Swipe(direction = SwipeDirection.Down, fromSource = SceneContainerEdge.TopRight) to - Overlays.QuickSettingsShade, - ) - } - - private fun swipeDownFromTopWithTwoFingers(): UserAction { - return Swipe(direction = SwipeDirection.Down, pointerCount = 2, fromSource = Edge.Top) - } - @AssistedFactory interface Factory { fun create(): GoneUserActionsViewModel diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt new file mode 100644 index 000000000000..65b6231705d4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt @@ -0,0 +1,77 @@ +/* + * 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.shade.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.UserAction +import com.android.compose.animation.scene.UserActionResult +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.scene.ui.viewmodel.SceneContainerEdge + +/** Returns collection of [UserAction] to [UserActionResult] pairs for opening the single shade. */ +fun singleShadeActions( + requireTwoPointersForTopEdgeForQs: Boolean = false +): Array<Pair<UserAction, UserActionResult>> { + return arrayOf( + // Swiping down, not from the edge, always goes to shade. + Swipe.Down to Scenes.Shade, + swipeDown(pointerCount = 2) to Scenes.Shade, + + // Swiping down from the top edge. + swipeDownFromTop(pointerCount = 1) to + if (requireTwoPointersForTopEdgeForQs) { + Scenes.Shade + } else { + Scenes.QuickSettings + }, + swipeDownFromTop(pointerCount = 2) to Scenes.QuickSettings, + ) +} + +/** Returns collection of [UserAction] to [UserActionResult] pairs for opening the split shade. */ +fun splitShadeActions(): Array<Pair<UserAction, UserActionResult>> { + val splitShadeSceneKey = UserActionResult(Scenes.Shade, ToSplitShade) + return arrayOf( + // Swiping down, not from the edge, always goes to shade. + Swipe.Down to splitShadeSceneKey, + swipeDown(pointerCount = 2) to splitShadeSceneKey, + // Swiping down from the top edge goes to QS. + swipeDownFromTop(pointerCount = 1) to splitShadeSceneKey, + swipeDownFromTop(pointerCount = 2) to splitShadeSceneKey, + ) +} + +/** Returns collection of [UserAction] to [UserActionResult] pairs for opening the dual shade. */ +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) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelKosmos.kt new file mode 100644 index 000000000000..1c84133d3821 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelKosmos.kt @@ -0,0 +1,29 @@ +/* + * 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.communal.ui.viewmodel + +import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.shade.domain.interactor.shadeInteractor + +val Kosmos.communalUserActionsViewModel by Fixture { + CommunalUserActionsViewModel( + deviceUnlockedInteractor = deviceUnlockedInteractor, + shadeInteractor = shadeInteractor, + ) +} |