summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockScreenSceneInteractor.kt186
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockScreenSceneViewModel.kt108
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockScreenSceneInteractorTest.kt270
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockScreenSceneViewModelTest.kt190
4 files changed, 754 insertions, 0 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockScreenSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockScreenSceneInteractor.kt
new file mode 100644
index 000000000000..61701802e231
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockScreenSceneInteractor.kt
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2023 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.keyguard.domain.interactor
+
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.util.kotlin.pairwise
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/** Hosts business and application state accessing logic for the lock screen scene. */
+class LockScreenSceneInteractor
+@AssistedInject
+constructor(
+ @Application applicationScope: CoroutineScope,
+ private val authenticationInteractor: AuthenticationInteractor,
+ bouncerInteractorFactory: BouncerInteractor.Factory,
+ private val sceneInteractor: SceneInteractor,
+ @Assisted private val containerName: String,
+) {
+ private val bouncerInteractor: BouncerInteractor =
+ bouncerInteractorFactory.create(containerName)
+
+ /** Whether the device is currently locked. */
+ val isDeviceLocked: StateFlow<Boolean> =
+ authenticationInteractor.isUnlocked
+ .map { !it }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = !authenticationInteractor.isUnlocked.value,
+ )
+
+ /** Whether it's currently possible to swipe up to dismiss the lock screen. */
+ val isSwipeToDismissEnabled: StateFlow<Boolean> =
+ combine(
+ authenticationInteractor.isUnlocked,
+ authenticationInteractor.authenticationMethod,
+ ) { isUnlocked, authMethod ->
+ isSwipeToUnlockEnabled(
+ isUnlocked = isUnlocked,
+ authMethod = authMethod,
+ )
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue =
+ isSwipeToUnlockEnabled(
+ isUnlocked = authenticationInteractor.isUnlocked.value,
+ authMethod = authenticationInteractor.authenticationMethod.value,
+ ),
+ )
+
+ init {
+ // LOCKING SHOWS LOCK SCREEN.
+ //
+ // Move to the lock screen scene if the device becomes locked while in any scene.
+ applicationScope.launch {
+ authenticationInteractor.isUnlocked
+ .map { !it }
+ .distinctUntilChanged()
+ .collect { isLocked ->
+ if (isLocked) {
+ sceneInteractor.setCurrentScene(
+ containerName = containerName,
+ scene = SceneModel(SceneKey.LockScreen),
+ )
+ }
+ }
+ }
+
+ // BYPASS UNLOCK.
+ //
+ // Moves to the gone scene if bypass is enabled and the device becomes unlocked while in the
+ // lock screen scene.
+ applicationScope.launch {
+ combine(
+ authenticationInteractor.isBypassEnabled,
+ authenticationInteractor.isUnlocked,
+ sceneInteractor.currentScene(containerName),
+ ::Triple,
+ )
+ .collect { (isBypassEnabled, isUnlocked, currentScene) ->
+ if (isBypassEnabled && isUnlocked && currentScene.key == SceneKey.LockScreen) {
+ sceneInteractor.setCurrentScene(
+ containerName = containerName,
+ scene = SceneModel(SceneKey.Gone),
+ )
+ }
+ }
+ }
+
+ // SWIPE TO DISMISS LOCK SCREEN.
+ //
+ // If switched from the lock screen to the gone scene and the auth method was a swipe,
+ // unlocks the device.
+ applicationScope.launch {
+ combine(
+ authenticationInteractor.authenticationMethod,
+ sceneInteractor.currentScene(containerName).pairwise(),
+ ::Pair,
+ )
+ .collect { (authMethod, scenes) ->
+ val (previousScene, currentScene) = scenes
+ if (
+ authMethod is AuthenticationMethodModel.Swipe &&
+ previousScene.key == SceneKey.LockScreen &&
+ currentScene.key == SceneKey.Gone
+ ) {
+ authenticationInteractor.unlockDevice()
+ }
+ }
+ }
+
+ // DISMISS LOCK SCREEN IF AUTH METHOD IS REMOVED.
+ //
+ // If the auth method becomes None while on the lock screen scene, dismisses the lock
+ // screen.
+ applicationScope.launch {
+ combine(
+ authenticationInteractor.authenticationMethod,
+ sceneInteractor.currentScene(containerName),
+ ::Pair,
+ )
+ .collect { (authMethod, scene) ->
+ if (
+ scene.key == SceneKey.LockScreen &&
+ authMethod == AuthenticationMethodModel.None
+ ) {
+ sceneInteractor.setCurrentScene(
+ containerName = containerName,
+ scene = SceneModel(SceneKey.Gone),
+ )
+ }
+ }
+ }
+ }
+
+ /** Attempts to dismiss the lock screen. This will cause the bouncer to show, if needed. */
+ fun dismissLockScreen() {
+ bouncerInteractor.showOrUnlockDevice(containerName = containerName)
+ }
+
+ private fun isSwipeToUnlockEnabled(
+ isUnlocked: Boolean,
+ authMethod: AuthenticationMethodModel,
+ ): Boolean {
+ return !isUnlocked && authMethod is AuthenticationMethodModel.Swipe
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ containerName: String,
+ ): LockScreenSceneInteractor
+ }
+}
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
new file mode 100644
index 000000000000..08b961306817
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockScreenSceneViewModel.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.viewmodel
+
+import com.android.systemui.R
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.domain.interactor.LockScreenSceneInteractor
+import com.android.systemui.scene.shared.model.SceneKey
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/** Models UI state and handles user input for the lock screen scene. */
+class LockScreenSceneViewModel
+@AssistedInject
+constructor(
+ @Application applicationScope: CoroutineScope,
+ interactorFactory: LockScreenSceneInteractor.Factory,
+ @Assisted containerName: String,
+) {
+ private val interactor: LockScreenSceneInteractor = interactorFactory.create(containerName)
+
+ /** The icon for the "lock" button on the lock screen. */
+ val lockButtonIcon: StateFlow<Icon> =
+ interactor.isDeviceLocked
+ .map { isLocked -> lockIcon(isLocked = isLocked) }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = lockIcon(isLocked = interactor.isDeviceLocked.value),
+ )
+
+ /** The key of the scene we should switch to when swiping up. */
+ val upDestinationSceneKey: StateFlow<SceneKey> =
+ interactor.isSwipeToDismissEnabled
+ .map { isSwipeToUnlockEnabled -> upDestinationSceneKey(isSwipeToUnlockEnabled) }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = upDestinationSceneKey(interactor.isSwipeToDismissEnabled.value),
+ )
+
+ /** Notifies that the lock button on the lock screen was clicked. */
+ fun onLockButtonClicked() {
+ interactor.dismissLockScreen()
+ }
+
+ /** Notifies that some content on the lock screen was clicked. */
+ fun onContentClicked() {
+ interactor.dismissLockScreen()
+ }
+
+ private fun upDestinationSceneKey(
+ isSwipeToUnlockEnabled: Boolean,
+ ): SceneKey {
+ return if (isSwipeToUnlockEnabled) SceneKey.Gone else SceneKey.Bouncer
+ }
+
+ private fun lockIcon(
+ isLocked: Boolean,
+ ): Icon {
+ return Icon.Resource(
+ res =
+ if (isLocked) {
+ R.drawable.ic_device_lock_on
+ } else {
+ R.drawable.ic_device_lock_off
+ },
+ contentDescription =
+ ContentDescription.Resource(
+ res =
+ if (isLocked) {
+ R.string.accessibility_lock_icon
+ } else {
+ R.string.accessibility_unlock_button
+ }
+ )
+ )
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ containerName: String,
+ ): LockScreenSceneViewModel
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockScreenSceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockScreenSceneInteractorTest.kt
new file mode 100644
index 000000000000..749e7a0481eb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockScreenSceneInteractorTest.kt
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2023 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.keyguard.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.AuthenticationRepositoryImpl
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.data.repo.BouncerRepository
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.scene.data.repository.fakeSceneContainerRepository
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+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
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class LockScreenSceneInteractorTest : SysuiTestCase() {
+
+ private val testScope = TestScope()
+ private val sceneInteractor =
+ SceneInteractor(
+ repository = fakeSceneContainerRepository(),
+ )
+ private val mAuthenticationInteractor =
+ AuthenticationInteractor(
+ applicationScope = testScope.backgroundScope,
+ repository = AuthenticationRepositoryImpl(),
+ )
+ private val underTest =
+ LockScreenSceneInteractor(
+ applicationScope = testScope.backgroundScope,
+ authenticationInteractor = mAuthenticationInteractor,
+ bouncerInteractorFactory =
+ object : BouncerInteractor.Factory {
+ override fun create(containerName: String): BouncerInteractor {
+ return BouncerInteractor(
+ applicationScope = testScope.backgroundScope,
+ applicationContext = context,
+ repository = BouncerRepository(),
+ authenticationInteractor = mAuthenticationInteractor,
+ sceneInteractor = sceneInteractor,
+ containerName = containerName,
+ )
+ }
+ },
+ sceneInteractor = sceneInteractor,
+ containerName = CONTAINER_NAME,
+ )
+
+ @Test
+ fun isDeviceLocked() =
+ testScope.runTest {
+ val isDeviceLocked by collectLastValue(underTest.isDeviceLocked)
+
+ mAuthenticationInteractor.lockDevice()
+ assertThat(isDeviceLocked).isTrue()
+
+ mAuthenticationInteractor.unlockDevice()
+ assertThat(isDeviceLocked).isFalse()
+ }
+
+ @Test
+ fun isSwipeToDismissEnabled_deviceLockedAndAuthMethodSwipe_true() =
+ testScope.runTest {
+ val isSwipeToDismissEnabled by collectLastValue(underTest.isSwipeToDismissEnabled)
+
+ mAuthenticationInteractor.lockDevice()
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+
+ assertThat(isSwipeToDismissEnabled).isTrue()
+ }
+
+ @Test
+ fun isSwipeToDismissEnabled_deviceUnlockedAndAuthMethodSwipe_false() =
+ testScope.runTest {
+ val isSwipeToDismissEnabled by collectLastValue(underTest.isSwipeToDismissEnabled)
+
+ mAuthenticationInteractor.unlockDevice()
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+
+ assertThat(isSwipeToDismissEnabled).isFalse()
+ }
+
+ @Test
+ fun dismissLockScreen_deviceLockedWithSecureAuthMethod_switchesToBouncer() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ mAuthenticationInteractor.lockDevice()
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
+
+ underTest.dismissLockScreen()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ }
+
+ @Test
+ fun dismissLockScreen_deviceUnlocked_switchesToGone() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ mAuthenticationInteractor.unlockDevice()
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
+
+ underTest.dismissLockScreen()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+ }
+
+ @Test
+ fun dismissLockScreen_deviceLockedWithInsecureAuthMethod_switchesToGone() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ mAuthenticationInteractor.lockDevice()
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
+
+ underTest.dismissLockScreen()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+ }
+
+ @Test
+ fun deviceLockedInNonLockScreenScene_switchesToLockScreenScene() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ runCurrent()
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Gone))
+ runCurrent()
+ mAuthenticationInteractor.unlockDevice()
+ runCurrent()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+
+ mAuthenticationInteractor.lockDevice()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
+ }
+
+ @Test
+ fun deviceBiometricUnlockedInLockScreen_bypassEnabled_switchesToGone() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ mAuthenticationInteractor.lockDevice()
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.LockScreen))
+ if (!mAuthenticationInteractor.isBypassEnabled.value) {
+ mAuthenticationInteractor.toggleBypassEnabled()
+ }
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
+
+ mAuthenticationInteractor.biometricUnlock()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+ }
+
+ @Test
+ fun deviceBiometricUnlockedInLockScreen_bypassNotEnabled_doesNotSwitch() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ mAuthenticationInteractor.lockDevice()
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.LockScreen))
+ if (mAuthenticationInteractor.isBypassEnabled.value) {
+ mAuthenticationInteractor.toggleBypassEnabled()
+ }
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
+
+ mAuthenticationInteractor.biometricUnlock()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
+ }
+
+ @Test
+ fun switchFromLockScreenToGone_authMethodSwipe_unlocksDevice() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(mAuthenticationInteractor.isUnlocked)
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.LockScreen))
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+ assertThat(isUnlocked).isFalse()
+
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Gone))
+
+ assertThat(isUnlocked).isTrue()
+ }
+
+ @Test
+ fun switchFromLockScreenToGone_authMethodNotSwipe_doesNotUnlockDevice() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(mAuthenticationInteractor.isUnlocked)
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.LockScreen))
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ assertThat(isUnlocked).isFalse()
+
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Gone))
+
+ assertThat(isUnlocked).isFalse()
+ }
+
+ @Test
+ fun switchFromNonLockScreenToGone_authMethodSwipe_doesNotUnlockDevice() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(mAuthenticationInteractor.isUnlocked)
+ runCurrent()
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Shade))
+ runCurrent()
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+ runCurrent()
+ assertThat(isUnlocked).isFalse()
+
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Gone))
+
+ assertThat(isUnlocked).isFalse()
+ }
+
+ @Test
+ fun authMethodChangedToNone_onLockScreenScene_dismissesLockScreen() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.LockScreen))
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
+
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.None)
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+ }
+
+ @Test
+ fun authMethodChangedToNone_notOnLockScreenScene_doesNotDismissLockScreen() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+ runCurrent()
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.QuickSettings))
+ runCurrent()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.QuickSettings))
+
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.None)
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.QuickSettings))
+ }
+
+ companion object {
+ private const val CONTAINER_NAME = "container1"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockScreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockScreenSceneViewModelTest.kt
new file mode 100644
index 000000000000..d335b09b196a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockScreenSceneViewModelTest.kt
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.AuthenticationRepositoryImpl
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.data.repo.BouncerRepository
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.domain.interactor.LockScreenSceneInteractor
+import com.android.systemui.scene.data.repository.fakeSceneContainerRepository
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+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
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class LockScreenSceneViewModelTest : SysuiTestCase() {
+
+ private val testScope = TestScope()
+ private val sceneInteractor =
+ SceneInteractor(
+ repository = fakeSceneContainerRepository(),
+ )
+ private val mAuthenticationInteractor =
+ AuthenticationInteractor(
+ applicationScope = testScope.backgroundScope,
+ repository = AuthenticationRepositoryImpl(),
+ )
+
+ private val underTest =
+ LockScreenSceneViewModel(
+ applicationScope = testScope.backgroundScope,
+ interactorFactory =
+ object : LockScreenSceneInteractor.Factory {
+ override fun create(containerName: String): LockScreenSceneInteractor {
+ return LockScreenSceneInteractor(
+ applicationScope = testScope.backgroundScope,
+ authenticationInteractor = mAuthenticationInteractor,
+ bouncerInteractorFactory =
+ object : BouncerInteractor.Factory {
+ override fun create(containerName: String): BouncerInteractor {
+ return BouncerInteractor(
+ applicationScope = testScope.backgroundScope,
+ applicationContext = context,
+ repository = BouncerRepository(),
+ authenticationInteractor = mAuthenticationInteractor,
+ sceneInteractor = sceneInteractor,
+ containerName = containerName,
+ )
+ }
+ },
+ sceneInteractor = sceneInteractor,
+ containerName = CONTAINER_NAME,
+ )
+ }
+ },
+ containerName = CONTAINER_NAME
+ )
+
+ @Test
+ fun lockButtonIcon_whenLocked() =
+ testScope.runTest {
+ val lockButtonIcon by collectLastValue(underTest.lockButtonIcon)
+ mAuthenticationInteractor.setAuthenticationMethod(
+ AuthenticationMethodModel.Password("password")
+ )
+ mAuthenticationInteractor.lockDevice()
+
+ assertThat((lockButtonIcon as? Icon.Resource)?.res)
+ .isEqualTo(R.drawable.ic_device_lock_on)
+ }
+
+ @Test
+ fun lockButtonIcon_whenUnlocked() =
+ testScope.runTest {
+ val lockButtonIcon by collectLastValue(underTest.lockButtonIcon)
+ mAuthenticationInteractor.setAuthenticationMethod(
+ AuthenticationMethodModel.Password("password")
+ )
+ mAuthenticationInteractor.unlockDevice()
+
+ assertThat((lockButtonIcon as? Icon.Resource)?.res)
+ .isEqualTo(R.drawable.ic_device_lock_off)
+ }
+
+ @Test
+ fun upTransitionSceneKey_swipeToUnlockedEnabled_gone() =
+ testScope.runTest {
+ val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+ mAuthenticationInteractor.lockDevice()
+
+ assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
+ }
+
+ @Test
+ fun upTransitionSceneKey_swipeToUnlockedNotEnabled_bouncer() =
+ testScope.runTest {
+ val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ mAuthenticationInteractor.lockDevice()
+
+ assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer)
+ }
+
+ @Test
+ fun onLockButtonClicked_deviceLockedSecurely_switchesToBouncer() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ mAuthenticationInteractor.lockDevice()
+ runCurrent()
+
+ underTest.onLockButtonClicked()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ }
+
+ @Test
+ fun onContentClicked_deviceUnlocked_switchesToGone() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ mAuthenticationInteractor.unlockDevice()
+ runCurrent()
+
+ underTest.onContentClicked()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+ }
+
+ @Test
+ fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ mAuthenticationInteractor.lockDevice()
+ runCurrent()
+
+ underTest.onContentClicked()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ }
+
+ @Test
+ fun onLockButtonClicked_deviceUnlocked_switchesToGone() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ mAuthenticationInteractor.unlockDevice()
+ runCurrent()
+
+ underTest.onLockButtonClicked()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+ }
+
+ companion object {
+ private const val CONTAINER_NAME = "container1"
+ }
+}