diff options
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" + } +} |