Loading packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/QuickSettingsShadeSceneModule.kt 0 → 100644 +29 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.scene import com.android.systemui.qs.ui.composable.QuickSettingsShadeScene import com.android.systemui.scene.shared.model.Scene import dagger.Binds import dagger.Module import dagger.multibindings.IntoSet @Module interface QuickSettingsShadeSceneModule { @Binds @IntoSet fun quickSettingsShade(scene: QuickSettingsShadeScene): Scene } packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt 0 → 100644 +71 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.qs.ui.composable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeSceneViewModel import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.composable.ComposableScene import com.android.systemui.shade.ui.composable.OverlayShade import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel import javax.inject.Inject import kotlinx.coroutines.flow.StateFlow @SysUISingleton class QuickSettingsShadeScene @Inject constructor( viewModel: QuickSettingsShadeSceneViewModel, private val overlayShadeViewModel: OverlayShadeViewModel, ) : ComposableScene { override val key = Scenes.QuickSettingsShade override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> = viewModel.destinationScenes @Composable override fun SceneScope.Content( modifier: Modifier, ) { OverlayShade( viewModel = overlayShadeViewModel, modifier = modifier, horizontalArrangement = Arrangement.End, ) { Text( text = "Quick settings grid", modifier = Modifier.padding(QuickSettingsShade.Dimensions.Padding) ) } } } object QuickSettingsShade { object Dimensions { val Padding = 16.dp } } packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt 0 → 100644 +124 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.qs.ui.viewmodel import android.testing.TestableLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.Swipe 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.data.repository.fakeDeviceEntryRepository 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.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.ui.viewmodel.quickSettingsShadeSceneViewModel import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper @EnableSceneContainer class QuickSettingsShadeSceneViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val sceneInteractor = kosmos.sceneInteractor private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor private val underTest = kosmos.quickSettingsShadeSceneViewModel @Test fun upTransitionSceneKey_deviceLocked_lockscreen() = testScope.runTest { val destinationScenes by collectLastValue(underTest.destinationScenes) lockDevice() assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(Scenes.Lockscreen) } @Test fun upTransitionSceneKey_deviceUnlocked_gone() = testScope.runTest { val destinationScenes by collectLastValue(underTest.destinationScenes) lockDevice() unlockDevice() assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(Scenes.Gone) } @Test fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() = testScope.runTest { val destinationScenes by collectLastValue(underTest.destinationScenes) kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.None ) sceneInteractor.changeScene(Scenes.Lockscreen, "reason") assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(Scenes.Lockscreen) } @Test fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() = testScope.runTest { val destinationScenes by collectLastValue(underTest.destinationScenes) kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.None ) runCurrent() sceneInteractor.changeScene(Scenes.Gone, "reason") assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(Scenes.Gone) } private fun TestScope.lockDevice() { val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus) kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(deviceUnlockStatus?.isUnlocked).isFalse() sceneInteractor.changeScene(Scenes.Lockscreen, "reason") runCurrent() } private fun TestScope.unlockDevice() { val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus) kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( SuccessFingerprintAuthenticationStatus(0, true) ) assertThat(deviceUnlockStatus?.isUnlocked).isTrue() sceneInteractor.changeScene(Scenes.Gone, "reason") runCurrent() } } packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt +7 −1 Original line number Diff line number Diff line Loading @@ -100,7 +100,13 @@ constructor( // 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, swipeDownFromTop(pointerCount = 2) to // TODO(b/338577208): Remove 'Dual' once we add Dual Shade invocation zones. if (shadeMode is ShadeMode.Dual) { Scenes.QuickSettingsShade } else { quickSettingsIfSingleShade }, // Swiping down, not from the edge, always navigates to the shade scene. swipeDown(pointerCount = 1) to shadeSceneKey, Loading packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt +4 −1 Original line number Diff line number Diff line Loading @@ -85,7 +85,10 @@ constructor( { it.scene == Scenes.NotificationsShade || it.scene == Scenes.Shade }, SYSUI_STATE_QUICK_SETTINGS_EXPANDED to { it.scene == Scenes.QuickSettings }, SYSUI_STATE_QUICK_SETTINGS_EXPANDED to { it.scene == Scenes.QuickSettingsShade || it.scene == Scenes.QuickSettings }, SYSUI_STATE_BOUNCER_SHOWING to { it.scene == Scenes.Bouncer }, SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING to { Loading Loading
packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/QuickSettingsShadeSceneModule.kt 0 → 100644 +29 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.scene import com.android.systemui.qs.ui.composable.QuickSettingsShadeScene import com.android.systemui.scene.shared.model.Scene import dagger.Binds import dagger.Module import dagger.multibindings.IntoSet @Module interface QuickSettingsShadeSceneModule { @Binds @IntoSet fun quickSettingsShade(scene: QuickSettingsShadeScene): Scene }
packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt 0 → 100644 +71 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.qs.ui.composable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeSceneViewModel import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.composable.ComposableScene import com.android.systemui.shade.ui.composable.OverlayShade import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel import javax.inject.Inject import kotlinx.coroutines.flow.StateFlow @SysUISingleton class QuickSettingsShadeScene @Inject constructor( viewModel: QuickSettingsShadeSceneViewModel, private val overlayShadeViewModel: OverlayShadeViewModel, ) : ComposableScene { override val key = Scenes.QuickSettingsShade override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> = viewModel.destinationScenes @Composable override fun SceneScope.Content( modifier: Modifier, ) { OverlayShade( viewModel = overlayShadeViewModel, modifier = modifier, horizontalArrangement = Arrangement.End, ) { Text( text = "Quick settings grid", modifier = Modifier.padding(QuickSettingsShade.Dimensions.Padding) ) } } } object QuickSettingsShade { object Dimensions { val Padding = 16.dp } }
packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt 0 → 100644 +124 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.qs.ui.viewmodel import android.testing.TestableLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.Swipe 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.data.repository.fakeDeviceEntryRepository 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.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.ui.viewmodel.quickSettingsShadeSceneViewModel import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper @EnableSceneContainer class QuickSettingsShadeSceneViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val sceneInteractor = kosmos.sceneInteractor private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor private val underTest = kosmos.quickSettingsShadeSceneViewModel @Test fun upTransitionSceneKey_deviceLocked_lockscreen() = testScope.runTest { val destinationScenes by collectLastValue(underTest.destinationScenes) lockDevice() assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(Scenes.Lockscreen) } @Test fun upTransitionSceneKey_deviceUnlocked_gone() = testScope.runTest { val destinationScenes by collectLastValue(underTest.destinationScenes) lockDevice() unlockDevice() assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(Scenes.Gone) } @Test fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() = testScope.runTest { val destinationScenes by collectLastValue(underTest.destinationScenes) kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.None ) sceneInteractor.changeScene(Scenes.Lockscreen, "reason") assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(Scenes.Lockscreen) } @Test fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() = testScope.runTest { val destinationScenes by collectLastValue(underTest.destinationScenes) kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.None ) runCurrent() sceneInteractor.changeScene(Scenes.Gone, "reason") assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(Scenes.Gone) } private fun TestScope.lockDevice() { val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus) kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(deviceUnlockStatus?.isUnlocked).isFalse() sceneInteractor.changeScene(Scenes.Lockscreen, "reason") runCurrent() } private fun TestScope.unlockDevice() { val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus) kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( SuccessFingerprintAuthenticationStatus(0, true) ) assertThat(deviceUnlockStatus?.isUnlocked).isTrue() sceneInteractor.changeScene(Scenes.Gone, "reason") runCurrent() } }
packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt +7 −1 Original line number Diff line number Diff line Loading @@ -100,7 +100,13 @@ constructor( // 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, swipeDownFromTop(pointerCount = 2) to // TODO(b/338577208): Remove 'Dual' once we add Dual Shade invocation zones. if (shadeMode is ShadeMode.Dual) { Scenes.QuickSettingsShade } else { quickSettingsIfSingleShade }, // Swiping down, not from the edge, always navigates to the shade scene. swipeDown(pointerCount = 1) to shadeSceneKey, Loading
packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt +4 −1 Original line number Diff line number Diff line Loading @@ -85,7 +85,10 @@ constructor( { it.scene == Scenes.NotificationsShade || it.scene == Scenes.Shade }, SYSUI_STATE_QUICK_SETTINGS_EXPANDED to { it.scene == Scenes.QuickSettings }, SYSUI_STATE_QUICK_SETTINGS_EXPANDED to { it.scene == Scenes.QuickSettingsShade || it.scene == Scenes.QuickSettings }, SYSUI_STATE_BOUNCER_SHOWING to { it.scene == Scenes.Bouncer }, SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING to { Loading