diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt index ca7352ef2501b..da48762e19603 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt @@ -63,7 +63,7 @@ constructor( .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, - initialValue = destinationScenes(up = viewModel.upDestinationSceneKey.value) + initialValue = destinationScenes(up = null) ) @Composable @@ -77,12 +77,12 @@ constructor( } private fun destinationScenes( - up: SceneKey, + up: SceneKey?, ): Map { - return mapOf( - UserAction.Swipe(Direction.UP) to SceneModel(up), - UserAction.Swipe(Direction.DOWN) to SceneModel(SceneKey.Shade) - ) + return buildMap { + up?.let { this[UserAction.Swipe(Direction.UP)] = SceneModel(up) } + this[UserAction.Swipe(Direction.DOWN)] = SceneModel(SceneKey.Shade) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/model/AuthenticationMethodModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/model/AuthenticationMethodModel.kt new file mode 100644 index 0000000000000..6d23b11e5d662 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/authentication/data/model/AuthenticationMethodModel.kt @@ -0,0 +1,37 @@ +/* + * Copyright 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.authentication.data.model + +/** Enumerates all known authentication methods. */ +sealed class AuthenticationMethodModel( + /** + * Whether the authentication method is considered to be "secure". + * + * "Secure" authentication methods require authentication to unlock the device. Non-secure auth + * methods simply require user dismissal. + */ + open val isSecure: Boolean, +) { + /** There is no authentication method on the device. We shouldn't even show the lock screen. */ + object None : AuthenticationMethodModel(isSecure = false) + + object Pin : AuthenticationMethodModel(isSecure = true) + + object Password : AuthenticationMethodModel(isSecure = true) + + object Pattern : AuthenticationMethodModel(isSecure = true) +} diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt index deb3d035d7532..8d1fc5d9d5582 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt @@ -14,15 +14,21 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.authentication.data.repository +import android.app.admin.DevicePolicyManager +import android.content.IntentFilter +import android.os.UserHandle import com.android.internal.widget.LockPatternChecker import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockscreenCredential import com.android.keyguard.KeyguardSecurityModel -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.data.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationResultModel import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel +import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyguard.data.repository.KeyguardRepository @@ -37,13 +43,17 @@ import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -54,9 +64,10 @@ interface AuthenticationRepository { * Whether the device is unlocked. * * A device that is not yet unlocked requires unlocking by completing an authentication - * challenge according to the current authentication method. - * - * Note that this state has no real bearing on whether the lockscreen is showing or dismissed. + * challenge according to the current authentication method, unless in cases when the current + * authentication method is not "secure" (for example, None); in such cases, the value of this + * flow will always be `true`, even if the lockscreen is showing and still needs to be dismissed + * by the user to proceed. */ val isUnlocked: StateFlow @@ -85,9 +96,30 @@ interface AuthenticationRepository { /** The current throttling state, as cached via [setThrottling]. */ val throttling: StateFlow + /** + * The currently-configured authentication method. This determines how the authentication + * challenge needs to be completed in order to unlock an otherwise locked device. + * + * Note: there may be other ways to unlock the device that "bypass" the need for this + * authentication challenge (notably, biometrics like fingerprint or face unlock). + * + * Note: by design, this is a [Flow] and not a [StateFlow]; a consumer who wishes to get a + * snapshot of the current authentication method without establishing a collector of the flow + * can do so by invoking [getAuthenticationMethod]. + */ + val authenticationMethod: Flow + /** * Returns the currently-configured authentication method. This determines how the - * authentication challenge is completed in order to unlock an otherwise locked device. + * authentication challenge needs to be completed in order to unlock an otherwise locked device. + * + * Note: there may be other ways to unlock the device that "bypass" the need for this + * authentication challenge (notably, biometrics like fingerprint or face unlock). + * + * Note: by design, this is offered as a convenience method alongside [authenticationMethod]. + * The flow should be used for code that wishes to stay up-to-date its logic as the + * authentication changes over time and this method should be used for simple code that only + * needs to check the current value. */ suspend fun getAuthenticationMethod(): AuthenticationMethodModel @@ -141,6 +173,7 @@ constructor( private val userRepository: UserRepository, keyguardRepository: KeyguardRepository, private val lockPatternUtils: LockPatternUtils, + broadcastDispatcher: BroadcastDispatcher, ) : AuthenticationRepository { override val isUnlocked = keyguardRepository.isKeyguardUnlocked @@ -148,7 +181,7 @@ constructor( override suspend fun isLockscreenEnabled(): Boolean { return withContext(backgroundDispatcher) { val selectedUserId = userRepository.selectedUserId - !lockPatternUtils.isLockPatternEnabled(selectedUserId) + !lockPatternUtils.isLockScreenDisabled(selectedUserId) } } @@ -172,18 +205,31 @@ constructor( private val UserRepository.selectedUserId: Int get() = getSelectedUserInfo().id + override val authenticationMethod: Flow = + userRepository.selectedUserInfo + .map { it.id } + .distinctUntilChanged() + .flatMapLatest { selectedUserId -> + broadcastDispatcher + .broadcastFlow( + filter = + IntentFilter( + DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED + ), + user = UserHandle.of(selectedUserId), + ) + .onStart { emit(Unit) } + .map { selectedUserId } + } + .map { selectedUserId -> + withContext(backgroundDispatcher) { + blockingAuthenticationMethodInternal(selectedUserId) + } + } + override suspend fun getAuthenticationMethod(): AuthenticationMethodModel { return withContext(backgroundDispatcher) { - val selectedUserId = userRepository.selectedUserId - when (getSecurityMode.apply(selectedUserId)) { - KeyguardSecurityModel.SecurityMode.PIN, - KeyguardSecurityModel.SecurityMode.SimPin, - KeyguardSecurityModel.SecurityMode.SimPuk -> AuthenticationMethodModel.Pin - KeyguardSecurityModel.SecurityMode.Password -> AuthenticationMethodModel.Password - KeyguardSecurityModel.SecurityMode.Pattern -> AuthenticationMethodModel.Pattern - KeyguardSecurityModel.SecurityMode.None -> AuthenticationMethodModel.None - KeyguardSecurityModel.SecurityMode.Invalid -> error("Invalid security mode!") - } + blockingAuthenticationMethodInternal(userRepository.selectedUserId) } } @@ -301,6 +347,27 @@ constructor( return flow.asStateFlow() } + + /** + * Returns the authentication method for the given user ID. + * + * WARNING: this is actually a blocking IPC/"binder" call that's expensive to do on the main + * thread. We keep it not marked as `suspend` because we want to be able to run this without a + * `runBlocking` which has a ton of performance/blocking problems. + */ + private fun blockingAuthenticationMethodInternal( + userId: Int, + ): AuthenticationMethodModel { + return when (getSecurityMode.apply(userId)) { + KeyguardSecurityModel.SecurityMode.PIN, + KeyguardSecurityModel.SecurityMode.SimPin, + KeyguardSecurityModel.SecurityMode.SimPuk -> AuthenticationMethodModel.Pin + KeyguardSecurityModel.SecurityMode.Password -> AuthenticationMethodModel.Password + KeyguardSecurityModel.SecurityMode.Pattern -> AuthenticationMethodModel.Pattern + KeyguardSecurityModel.SecurityMode.None -> AuthenticationMethodModel.None + KeyguardSecurityModel.SecurityMode.Invalid -> error("Invalid security mode!") + } + } } @Module diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt index d4371bf30e0ea..75192021dab6c 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt @@ -18,8 +18,10 @@ package com.android.systemui.authentication.domain.interactor import com.android.internal.widget.LockPatternView import com.android.internal.widget.LockscreenCredential +import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel import com.android.systemui.authentication.data.repository.AuthenticationRepository -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -35,8 +37,10 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.async import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow 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 @@ -55,22 +59,41 @@ constructor( private val keyguardRepository: KeyguardRepository, private val clock: SystemClock, ) { + /** + * The currently-configured authentication method. This determines how the authentication + * challenge needs to be completed in order to unlock an otherwise locked device. + * + * Note: there may be other ways to unlock the device that "bypass" the need for this + * authentication challenge (notably, biometrics like fingerprint or face unlock). + * + * Note: by design, this is a [Flow] and not a [StateFlow]; a consumer who wishes to get a + * snapshot of the current authentication method without establishing a collector of the flow + * can do so by invoking [getAuthenticationMethod]. + * + * Note: this layer adds the synthetic authentication method of "swipe" which is special. When + * the current authentication method is "swipe", the user does not need to complete any + * authentication challenge to unlock the device; they just need to dismiss the lockscreen to + * get past it. This also means that the value of [isUnlocked] remains `false` even when the + * lockscreen is showing and still needs to be dismissed by the user to proceed. + */ + val authenticationMethod: Flow = + repository.authenticationMethod.map { rawModel -> rawModel.toDomainLayer() } + /** * Whether the device is unlocked. * * A device that is not yet unlocked requires unlocking by completing an authentication - * challenge according to the current authentication method. - * - * Note that this state has no real bearing on whether the lock screen is showing or dismissed. + * challenge according to the current authentication method, unless in cases when the current + * authentication method is not "secure" (for example, None and Swipe); in such cases, the value + * of this flow will always be `true`, even if the lockscreen is showing and still needs to be + * dismissed by the user to proceed. */ val isUnlocked: StateFlow = - repository.isUnlocked - .map { isUnlocked -> - if (getAuthenticationMethod() is AuthenticationMethodModel.None) { - true - } else { - isUnlocked - } + combine( + repository.isUnlocked, + authenticationMethod, + ) { isUnlocked, authenticationMethod -> + authenticationMethod is DomainLayerAuthenticationMethodModel.None || isUnlocked } .stateIn( scope = applicationScope, @@ -129,18 +152,24 @@ constructor( /** * Returns the currently-configured authentication method. This determines how the - * authentication challenge is completed in order to unlock an otherwise locked device. + * authentication challenge needs to be completed in order to unlock an otherwise locked device. + * + * Note: there may be other ways to unlock the device that "bypass" the need for this + * authentication challenge (notably, biometrics like fingerprint or face unlock). + * + * Note: by design, this is offered as a convenience method alongside [authenticationMethod]. + * The flow should be used for code that wishes to stay up-to-date its logic as the + * authentication changes over time and this method should be used for simple code that only + * needs to check the current value. + * + * Note: this layer adds the synthetic authentication method of "swipe" which is special. When + * the current authentication method is "swipe", the user does not need to complete any + * authentication challenge to unlock the device; they just need to dismiss the lockscreen to + * get past it. This also means that the value of [isUnlocked] remains `false` even when the + * lockscreen is showing and still needs to be dismissed by the user to proceed. */ - suspend fun getAuthenticationMethod(): AuthenticationMethodModel { - val authMethod = repository.getAuthenticationMethod() - return if ( - authMethod is AuthenticationMethodModel.None && repository.isLockscreenEnabled() - ) { - // We treat "None" as "Swipe" when the lockscreen is enabled. - AuthenticationMethodModel.Swipe - } else { - authMethod - } + suspend fun getAuthenticationMethod(): DomainLayerAuthenticationMethodModel { + return repository.getAuthenticationMethod().toDomainLayer() } /** @@ -270,21 +299,38 @@ constructor( } } - private fun AuthenticationMethodModel.createCredential( + private fun DomainLayerAuthenticationMethodModel.createCredential( input: List ): LockscreenCredential? { return when (this) { - is AuthenticationMethodModel.Pin -> + is DomainLayerAuthenticationMethodModel.Pin -> LockscreenCredential.createPin(input.joinToString("")) - is AuthenticationMethodModel.Password -> + is DomainLayerAuthenticationMethodModel.Password -> LockscreenCredential.createPassword(input.joinToString("")) - is AuthenticationMethodModel.Pattern -> + is DomainLayerAuthenticationMethodModel.Pattern -> LockscreenCredential.createPattern( input - .map { it as AuthenticationMethodModel.Pattern.PatternCoordinate } + .map { it as AuthenticationPatternCoordinate } .map { LockPatternView.Cell.of(it.y, it.x) } ) else -> null } } + + private suspend fun DataLayerAuthenticationMethodModel.toDomainLayer(): + DomainLayerAuthenticationMethodModel { + return when (this) { + is DataLayerAuthenticationMethodModel.None -> + if (repository.isLockscreenEnabled()) { + DomainLayerAuthenticationMethodModel.Swipe + } else { + DomainLayerAuthenticationMethodModel.None + } + is DataLayerAuthenticationMethodModel.Pin -> DomainLayerAuthenticationMethodModel.Pin + is DataLayerAuthenticationMethodModel.Password -> + DomainLayerAuthenticationMethodModel.Password + is DataLayerAuthenticationMethodModel.Pattern -> + DomainLayerAuthenticationMethodModel.Pattern + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/model/AuthenticationMethodModel.kt similarity index 86% rename from packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt rename to packages/SystemUI/src/com/android/systemui/authentication/domain/model/AuthenticationMethodModel.kt index 97c6697f10a5b..d7e6099a89082 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/model/AuthenticationMethodModel.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.authentication.shared.model +package com.android.systemui.authentication.domain.model /** Enumerates all known authentication methods. */ sealed class AuthenticationMethodModel( @@ -36,10 +36,5 @@ sealed class AuthenticationMethodModel( object Password : AuthenticationMethodModel(isSecure = true) - object Pattern : AuthenticationMethodModel(isSecure = true) { - data class PatternCoordinate( - val x: Int, - val y: Int, - ) - } + object Pattern : AuthenticationMethodModel(isSecure = true) } diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationPatternCoordinate.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationPatternCoordinate.kt new file mode 100644 index 0000000000000..8a3f780b3e54a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationPatternCoordinate.kt @@ -0,0 +1,22 @@ +/* + * Copyright 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.authentication.shared.model + +data class AuthenticationPatternCoordinate( + val x: Int, + val y: Int, +) diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt index 8ed964d4af221..ffcae1cacb00b 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt @@ -14,14 +14,12 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.bouncer.domain.interactor import android.content.Context import com.android.systemui.R import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.domain.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.bouncer.data.repository.BouncerRepository import com.android.systemui.dagger.SysUISingleton @@ -35,7 +33,6 @@ import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine @@ -106,14 +103,6 @@ constructor( } } - /** - * Returns the currently-configured authentication method. This determines how the - * authentication challenge is completed in order to unlock an otherwise locked device. - */ - suspend fun getAuthenticationMethod(): AuthenticationMethodModel { - return authenticationInteractor.getAuthenticationMethod() - } - /** * Either shows the bouncer or unlocks the device, if the bouncer doesn't need to be shown. * @@ -124,7 +113,9 @@ constructor( ) { applicationScope.launch { if (authenticationInteractor.isAuthenticationRequired()) { - repository.setMessage(message ?: promptMessage(getAuthenticationMethod())) + repository.setMessage( + message ?: promptMessage(authenticationInteractor.getAuthenticationMethod()) + ) sceneInteractor.setCurrentScene( scene = SceneModel(SceneKey.Bouncer), loggingReason = "request to unlock device while authentication required", @@ -143,7 +134,9 @@ constructor( * method. */ fun resetMessage() { - applicationScope.launch { repository.setMessage(promptMessage(getAuthenticationMethod())) } + applicationScope.launch { + repository.setMessage(promptMessage(authenticationInteractor.getAuthenticationMethod())) + } } /** Removes the user-facing message. */ @@ -181,7 +174,7 @@ constructor( loggingReason = "successful authentication", ) } else { - repository.setMessage(errorMessage(getAuthenticationMethod())) + repository.setMessage(errorMessage(authenticationInteractor.getAuthenticationMethod())) } return isAuthenticated diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt index 68e1a29bc6094..5b1998d1e5f6d 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt @@ -14,25 +14,20 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.bouncer.ui.viewmodel import android.content.Context import com.android.systemui.R -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor +import com.android.systemui.authentication.domain.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags -import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject import kotlin.math.ceil import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.channels.BufferOverflow -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -50,23 +45,24 @@ class BouncerViewModel constructor( @Application private val applicationContext: Context, @Application private val applicationScope: CoroutineScope, - private val interactor: BouncerInteractor, + private val bouncerInteractor: BouncerInteractor, + private val authenticationInteractor: AuthenticationInteractor, featureFlags: FeatureFlags, ) { private val isInputEnabled: StateFlow = - interactor.isThrottled + bouncerInteractor.isThrottled .map { !it } .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), - initialValue = !interactor.isThrottled.value, + initialValue = !bouncerInteractor.isThrottled.value, ) private val pin: PinBouncerViewModel by lazy { PinBouncerViewModel( applicationContext = applicationContext, applicationScope = applicationScope, - interactor = interactor, + interactor = bouncerInteractor, isInputEnabled = isInputEnabled, ) } @@ -74,7 +70,7 @@ constructor( private val password: PasswordBouncerViewModel by lazy { PasswordBouncerViewModel( applicationScope = applicationScope, - interactor = interactor, + interactor = bouncerInteractor, isInputEnabled = isInputEnabled, ) } @@ -83,31 +79,35 @@ constructor( PatternBouncerViewModel( applicationContext = applicationContext, applicationScope = applicationScope, - interactor = interactor, + interactor = bouncerInteractor, isInputEnabled = isInputEnabled, ) } /** View-model for the current UI, based on the current authentication method. */ - private val _authMethod = - MutableSharedFlow( - replay = 1, - onBufferOverflow = BufferOverflow.DROP_OLDEST, - ) val authMethod: StateFlow = - _authMethod.stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = null, - ) + authenticationInteractor.authenticationMethod + .map { authenticationMethod -> + when (authenticationMethod) { + is AuthenticationMethodModel.Pin -> pin + is AuthenticationMethodModel.Password -> password + is AuthenticationMethodModel.Pattern -> pattern + else -> null + } + } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = null, + ) init { if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) { applicationScope.launch { - interactor.isThrottled + bouncerInteractor.isThrottled .map { isThrottled -> if (isThrottled) { - when (interactor.getAuthenticationMethod()) { + when (authenticationInteractor.getAuthenticationMethod()) { is AuthenticationMethodModel.Pin -> R.string.kg_too_many_failed_pin_attempts_dialog_message is AuthenticationMethodModel.Password -> @@ -118,8 +118,9 @@ constructor( }?.let { stringResourceId -> applicationContext.getString( stringResourceId, - interactor.throttling.value.failedAttemptCount, - ceil(interactor.throttling.value.remainingMs / 1000f).toInt(), + bouncerInteractor.throttling.value.failedAttemptCount, + ceil(bouncerInteractor.throttling.value.remainingMs / 1000f) + .toInt(), ) } } else { @@ -133,25 +134,14 @@ constructor( } } } - - applicationScope.launch { - _authMethod.subscriptionCount - .pairwise() - .map { (previousCount, currentCount) -> currentCount > previousCount } - .collect { subscriberAdded -> - if (subscriberAdded) { - reloadAuthMethod() - } - } - } } } /** The user-facing message to show in the bouncer. */ val message: StateFlow = combine( - interactor.message, - interactor.isThrottled, + bouncerInteractor.message, + bouncerInteractor.isThrottled, ) { message, isThrottled -> toMessageViewModel(message, isThrottled) } @@ -160,8 +150,8 @@ constructor( started = SharingStarted.WhileSubscribed(), initialValue = toMessageViewModel( - message = interactor.message.value, - isThrottled = interactor.isThrottled.value, + message = bouncerInteractor.message.value, + isThrottled = bouncerInteractor.isThrottled.value, ), ) @@ -197,17 +187,6 @@ constructor( ) } - private suspend fun reloadAuthMethod() { - _authMethod.tryEmit( - when (interactor.getAuthenticationMethod()) { - is AuthenticationMethodModel.Pin -> pin - is AuthenticationMethodModel.Password -> password - is AuthenticationMethodModel.Pattern -> pattern - else -> null - } - ) - } - data class MessageViewModel( val text: String, diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt index 4be539d9396d1..4425f9ffcb5e0 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt @@ -18,7 +18,7 @@ package com.android.systemui.bouncer.ui.viewmodel import android.content.Context import android.util.TypedValue -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import kotlin.math.max import kotlin.math.min @@ -193,8 +193,8 @@ data class PatternDotViewModel( val x: Int, val y: Int, ) { - fun toCoordinate(): AuthenticationMethodModel.Pattern.PatternCoordinate { - return AuthenticationMethodModel.Pattern.PatternCoordinate( + fun toCoordinate(): AuthenticationPatternCoordinate { + return AuthenticationPatternCoordinate( x = x, y = y, ) 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 deleted file mode 100644 index 278c68d3c55ba..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application -import javax.inject.Inject -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn - -/** Hosts business and application state accessing logic for the lockscreen scene. */ -@SysUISingleton -class LockscreenSceneInteractor -@Inject -constructor( - @Application applicationScope: CoroutineScope, - private val authenticationInteractor: AuthenticationInteractor, - private val bouncerInteractor: BouncerInteractor, -) { - /** Whether the device is currently locked. */ - val isDeviceLocked: StateFlow = - 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 lockscreen. */ - val isSwipeToDismissEnabled: StateFlow = - authenticationInteractor.isUnlocked - .map { isUnlocked -> - !isUnlocked && - authenticationInteractor.getAuthenticationMethod() is - AuthenticationMethodModel.Swipe - } - .stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = false, - ) - - /** Attempts to dismiss the lockscreen. This will cause the bouncer to show, if needed. */ - fun dismissLockscreen() { - bouncerInteractor.showOrUnlockDevice() - } -} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt index abd178ca6c1d7..f46d0eb449a03 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt @@ -17,14 +17,17 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.R +import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor +import com.android.systemui.authentication.domain.model.AuthenticationMethodModel +import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor import com.android.systemui.scene.shared.model.SceneKey import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map @@ -36,36 +39,37 @@ class LockscreenSceneViewModel @Inject constructor( @Application applicationScope: CoroutineScope, - private val interactor: LockscreenSceneInteractor, + authenticationInteractor: AuthenticationInteractor, + private val bouncerInteractor: BouncerInteractor, ) { /** The icon for the "lock" button on the lockscreen. */ val lockButtonIcon: StateFlow = - interactor.isDeviceLocked - .map { isLocked -> lockIcon(isLocked = isLocked) } + authenticationInteractor.isUnlocked + .map { isUnlocked -> lockIcon(isUnlocked = isUnlocked) } .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), - initialValue = lockIcon(isLocked = interactor.isDeviceLocked.value), + initialValue = lockIcon(isUnlocked = authenticationInteractor.isUnlocked.value), ) /** The key of the scene we should switch to when swiping up. */ - val upDestinationSceneKey: StateFlow = - interactor.isSwipeToDismissEnabled - .map { isSwipeToUnlockEnabled -> upDestinationSceneKey(isSwipeToUnlockEnabled) } - .stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = upDestinationSceneKey(interactor.isSwipeToDismissEnabled.value), - ) + val upDestinationSceneKey: Flow = + authenticationInteractor.authenticationMethod.map { authenticationMethod -> + if (authenticationMethod is AuthenticationMethodModel.Swipe) { + SceneKey.Gone + } else { + SceneKey.Bouncer + } + } /** Notifies that the lock button on the lock screen was clicked. */ fun onLockButtonClicked() { - interactor.dismissLockscreen() + bouncerInteractor.showOrUnlockDevice() } /** Notifies that some content on the lock screen was clicked. */ fun onContentClicked() { - interactor.dismissLockscreen() + bouncerInteractor.showOrUnlockDevice() } private fun upDestinationSceneKey( @@ -75,22 +79,22 @@ constructor( } private fun lockIcon( - isLocked: Boolean, + isUnlocked: Boolean, ): Icon { return Icon.Resource( res = - if (isLocked) { - R.drawable.ic_device_lock_on - } else { + if (isUnlocked) { R.drawable.ic_device_lock_off + } else { + R.drawable.ic_device_lock_on }, contentDescription = ContentDescription.Resource( res = - if (isLocked) { - R.string.accessibility_lock_icon - } else { + if (isUnlocked) { R.string.accessibility_unlock_button + } else { + R.string.accessibility_lock_icon } ) ) diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt index 5e6a44bf81305..4c6281e1cdb08 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt @@ -16,19 +16,17 @@ package com.android.systemui.qs.ui.viewmodel +import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor import javax.inject.Inject /** Models UI state and handles user input for the quick settings scene. */ @SysUISingleton class QuickSettingsSceneViewModel @Inject -constructor( - private val lockscreenSceneInteractor: LockscreenSceneInteractor, -) { +constructor(private val bouncerInteractor: BouncerInteractor) { /** Notifies that some content in quick settings was clicked. */ fun onContentClicked() { - lockscreenSceneInteractor.dismissLockscreen() + bouncerInteractor.showOrUnlockDevice() } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt index 0b3ed5601c2e3..87abc9208d45c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt @@ -16,9 +16,10 @@ package com.android.systemui.shade.ui.viewmodel +import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor +import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor import com.android.systemui.scene.shared.model.SceneKey import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -33,29 +34,30 @@ class ShadeSceneViewModel @Inject constructor( @Application private val applicationScope: CoroutineScope, - private val lockscreenSceneInteractor: LockscreenSceneInteractor, + authenticationInteractor: AuthenticationInteractor, + private val bouncerInteractor: BouncerInteractor, ) { /** The key of the scene we should switch to when swiping up. */ val upDestinationSceneKey: StateFlow = - lockscreenSceneInteractor.isDeviceLocked - .map { isLocked -> upDestinationSceneKey(isLocked = isLocked) } + authenticationInteractor.isUnlocked + .map { isUnlocked -> upDestinationSceneKey(isUnlocked = isUnlocked) } .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), initialValue = upDestinationSceneKey( - isLocked = lockscreenSceneInteractor.isDeviceLocked.value, + isUnlocked = authenticationInteractor.isUnlocked.value, ), ) /** Notifies that some content in the shade was clicked. */ fun onContentClicked() { - lockscreenSceneInteractor.dismissLockscreen() + bouncerInteractor.showOrUnlockDevice() } private fun upDestinationSceneKey( - isLocked: Boolean, + isUnlocked: Boolean, ): SceneKey { - return if (isLocked) SceneKey.Lockscreen else SceneKey.Gone + return if (isUnlocked) SceneKey.Gone else SceneKey.Lockscreen } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt index 005697044c0fb..d3a2a73959dd3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt @@ -18,23 +18,30 @@ package com.android.systemui.authentication.data.repository +import android.app.admin.DevicePolicyManager +import android.content.Intent import android.content.pm.UserInfo import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.keyguard.KeyguardSecurityModel import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.model.AuthenticationMethodModel +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.scene.SceneTestUtils import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat +import java.util.function.Function import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 +import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.MockitoAnnotations @@ -43,6 +50,7 @@ import org.mockito.MockitoAnnotations class AuthenticationRepositoryTest : SysuiTestCase() { @Mock private lateinit var lockPatternUtils: LockPatternUtils + @Mock private lateinit var getSecurityMode: Function private val testUtils = SceneTestUtils(this) private val testScope = testUtils.testScope @@ -50,23 +58,48 @@ class AuthenticationRepositoryTest : SysuiTestCase() { private lateinit var underTest: AuthenticationRepository + private var currentSecurityMode: KeyguardSecurityModel.SecurityMode = + KeyguardSecurityModel.SecurityMode.PIN + @Before fun setUp() { MockitoAnnotations.initMocks(this) userRepository.setUserInfos(USER_INFOS) runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[0]) } + whenever(getSecurityMode.apply(anyInt())).thenAnswer { currentSecurityMode } underTest = AuthenticationRepositoryImpl( applicationScope = testScope.backgroundScope, - getSecurityMode = { KeyguardSecurityModel.SecurityMode.PIN }, + getSecurityMode = getSecurityMode, backgroundDispatcher = testUtils.testDispatcher, userRepository = userRepository, keyguardRepository = testUtils.keyguardRepository, lockPatternUtils = lockPatternUtils, + broadcastDispatcher = fakeBroadcastDispatcher, ) } + @Test + fun authenticationMethod() = + testScope.runTest { + val authMethod by collectLastValue(underTest.authenticationMethod) + runCurrent() + dispatchBroadcast() + assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Pin) + assertThat(underTest.getAuthenticationMethod()).isEqualTo(AuthenticationMethodModel.Pin) + + setSecurityModeAndDispatchBroadcast(KeyguardSecurityModel.SecurityMode.Pattern) + assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Pattern) + assertThat(underTest.getAuthenticationMethod()) + .isEqualTo(AuthenticationMethodModel.Pattern) + + setSecurityModeAndDispatchBroadcast(KeyguardSecurityModel.SecurityMode.None) + assertThat(authMethod).isEqualTo(AuthenticationMethodModel.None) + assertThat(underTest.getAuthenticationMethod()) + .isEqualTo(AuthenticationMethodModel.None) + } + @Test fun isAutoConfirmEnabled() = testScope.runTest { @@ -95,6 +128,20 @@ class AuthenticationRepositoryTest : SysuiTestCase() { assertThat(values.last()).isTrue() } + private fun setSecurityModeAndDispatchBroadcast( + securityMode: KeyguardSecurityModel.SecurityMode, + ) { + currentSecurityMode = securityMode + dispatchBroadcast() + } + + private fun dispatchBroadcast() { + fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED) + ) + } + companion object { private val USER_INFOS = listOf( diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt index a86937fcad3c3..d848cd46e5092 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt @@ -19,9 +19,11 @@ package com.android.systemui.authentication.domain.interactor import android.app.admin.DevicePolicyManager import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel import com.android.systemui.authentication.data.repository.AuthenticationRepository import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils @@ -50,42 +52,61 @@ class AuthenticationInteractorTest : SysuiTestCase() { ) @Test - fun getAuthenticationMethod() = + fun authenticationMethod() = testScope.runTest { - assertThat(underTest.getAuthenticationMethod()).isEqualTo(AuthenticationMethodModel.Pin) + val authMethod by collectLastValue(underTest.authenticationMethod) + runCurrent() + assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.Pin) + assertThat(underTest.getAuthenticationMethod()) + .isEqualTo(DomainLayerAuthenticationMethodModel.Pin) utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password + DataLayerAuthenticationMethodModel.Password ) + assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.Password) assertThat(underTest.getAuthenticationMethod()) - .isEqualTo(AuthenticationMethodModel.Password) + .isEqualTo(DomainLayerAuthenticationMethodModel.Password) } @Test - fun getAuthenticationMethod_noneTreatedAsSwipe_whenLockscreenEnabled() = + fun authenticationMethod_noneTreatedAsSwipe_whenLockscreenEnabled() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + val authMethod by collectLastValue(underTest.authenticationMethod) + runCurrent() + + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.None + ) utils.authenticationRepository.setLockscreenEnabled(true) + assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.Swipe) assertThat(underTest.getAuthenticationMethod()) - .isEqualTo(AuthenticationMethodModel.Swipe) + .isEqualTo(DomainLayerAuthenticationMethodModel.Swipe) } @Test - fun getAuthenticationMethod_none_whenLockscreenDisabled() = + fun authenticationMethod_none_whenLockscreenDisabled() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + val authMethod by collectLastValue(underTest.authenticationMethod) + runCurrent() + + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.None + ) utils.authenticationRepository.setLockscreenEnabled(false) + assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.None) assertThat(underTest.getAuthenticationMethod()) - .isEqualTo(AuthenticationMethodModel.None) + .isEqualTo(DomainLayerAuthenticationMethodModel.None) } @Test fun isUnlocked_whenAuthMethodIsNoneAndLockscreenDisabled_isTrue() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.None + ) utils.authenticationRepository.setLockscreenEnabled(false) val isUnlocked by collectLastValue(underTest.isUnlocked) @@ -111,7 +132,9 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun isUnlocked_whenAuthMethodIsNoneAndLockscreenEnabled_isFalse() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.None + ) utils.authenticationRepository.setLockscreenEnabled(true) val isUnlocked by collectLastValue(underTest.isUnlocked) @@ -124,7 +147,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { utils.authenticationRepository.setUnlocked(false) runCurrent() utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password + DataLayerAuthenticationMethodModel.Password ) assertThat(underTest.isAuthenticationRequired()).isTrue() @@ -135,7 +158,9 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { utils.authenticationRepository.setUnlocked(false) runCurrent() - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.None + ) assertThat(underTest.isAuthenticationRequired()).isFalse() } @@ -146,7 +171,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { utils.authenticationRepository.setUnlocked(true) runCurrent() utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password + DataLayerAuthenticationMethodModel.Password ) assertThat(underTest.isAuthenticationRequired()).isFalse() @@ -157,7 +182,9 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { utils.authenticationRepository.setUnlocked(true) runCurrent() - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.None + ) assertThat(underTest.isAuthenticationRequired()).isFalse() } @@ -166,7 +193,9 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun authenticate_withCorrectPin_returnsTrue() = testScope.runTest { val isThrottled by collectLastValue(underTest.isThrottled) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isTrue() assertThat(isThrottled).isFalse() } @@ -174,21 +203,27 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun authenticate_withIncorrectPin_returnsFalse() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) assertThat(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4))).isFalse() } @Test(expected = IllegalArgumentException::class) fun authenticate_withEmptyPin_throwsException() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) underTest.authenticate(listOf()) } @Test fun authenticate_withCorrectMaxLengthPin_returnsTrue() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) val pin = List(16) { 9 } utils.authenticationRepository.overrideCredential(pin) assertThat(underTest.authenticate(pin)).isTrue() @@ -203,7 +238,9 @@ class AuthenticationInteractorTest : SysuiTestCase() { // If the policy changes, there is work to do in SysUI. assertThat(DevicePolicyManager.MAX_PASSWORD_LENGTH).isLessThan(17) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) assertThat(underTest.authenticate(List(17) { 9 })).isFalse() } @@ -212,7 +249,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { val isThrottled by collectLastValue(underTest.isThrottled) utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password + DataLayerAuthenticationMethodModel.Password ) assertThat(underTest.authenticate("password".toList())).isTrue() @@ -223,7 +260,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun authenticate_withIncorrectPassword_returnsFalse() = testScope.runTest { utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password + DataLayerAuthenticationMethodModel.Password ) assertThat(underTest.authenticate("alohomora".toList())).isFalse() @@ -233,7 +270,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun authenticate_withCorrectPattern_returnsTrue() = testScope.runTest { utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pattern + DataLayerAuthenticationMethodModel.Pattern ) assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN)).isTrue() @@ -243,21 +280,21 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun authenticate_withIncorrectPattern_returnsFalse() = testScope.runTest { utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pattern + DataLayerAuthenticationMethodModel.Pattern ) assertThat( underTest.authenticate( listOf( - AuthenticationMethodModel.Pattern.PatternCoordinate( + AuthenticationPatternCoordinate( x = 2, y = 0, ), - AuthenticationMethodModel.Pattern.PatternCoordinate( + AuthenticationPatternCoordinate( x = 2, y = 1, ), - AuthenticationMethodModel.Pattern.PatternCoordinate( + AuthenticationPatternCoordinate( x = 2, y = 2, ), @@ -271,7 +308,9 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNullAndHasNoEffect() = testScope.runTest { val isThrottled by collectLastValue(underTest.isThrottled) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) utils.authenticationRepository.setAutoConfirmEnabled(true) assertThat( underTest.authenticate( @@ -289,7 +328,9 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun tryAutoConfirm_withAutoConfirmWrongPinCorrectLength_returnsFalseAndDoesNotUnlockDevice() = testScope.runTest { val isUnlocked by collectLastValue(underTest.isUnlocked) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) utils.authenticationRepository.setAutoConfirmEnabled(true) assertThat( underTest.authenticate( @@ -305,7 +346,9 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun tryAutoConfirm_withAutoConfirmLongerPin_returnsFalseAndDoesNotUnlockDevice() = testScope.runTest { val isUnlocked by collectLastValue(underTest.isUnlocked) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) utils.authenticationRepository.setAutoConfirmEnabled(true) assertThat( underTest.authenticate( @@ -321,7 +364,9 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun tryAutoConfirm_withAutoConfirmCorrectPin_returnsTrueAndUnlocksDevice() = testScope.runTest { val isUnlocked by collectLastValue(underTest.isUnlocked) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) utils.authenticationRepository.setAutoConfirmEnabled(true) assertThat( underTest.authenticate( @@ -337,7 +382,9 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNullAndHasNoEffects() = testScope.runTest { val isUnlocked by collectLastValue(underTest.isUnlocked) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) utils.authenticationRepository.setAutoConfirmEnabled(false) assertThat( underTest.authenticate( @@ -354,7 +401,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { val isUnlocked by collectLastValue(underTest.isUnlocked) utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password + DataLayerAuthenticationMethodModel.Password ) assertThat(underTest.authenticate("password".toList(), tryAutoConfirm = true)).isNull() @@ -367,7 +414,9 @@ class AuthenticationInteractorTest : SysuiTestCase() { val isUnlocked by collectLastValue(underTest.isUnlocked) val throttling by collectLastValue(underTest.throttling) val isThrottled by collectLastValue(underTest.isThrottled) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN) assertThat(isUnlocked).isTrue() assertThat(isThrottled).isFalse() @@ -456,7 +505,9 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun hintedPinLength_withoutAutoConfirm_isNull() = testScope.runTest { val hintedPinLength by collectLastValue(underTest.hintedPinLength) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) utils.authenticationRepository.setAutoConfirmEnabled(false) assertThat(hintedPinLength).isNull() @@ -466,7 +517,9 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun hintedPinLength_withAutoConfirmPinTooShort_isNull() = testScope.runTest { val hintedPinLength by collectLastValue(underTest.hintedPinLength) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) utils.authenticationRepository.overrideCredential( buildList { repeat(utils.authenticationRepository.hintedPinLength - 1) { add(it + 1) } @@ -481,7 +534,9 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun hintedPinLength_withAutoConfirmPinAtRightLength_isSameLength() = testScope.runTest { val hintedPinLength by collectLastValue(underTest.hintedPinLength) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) utils.authenticationRepository.setAutoConfirmEnabled(true) utils.authenticationRepository.overrideCredential( buildList { repeat(utils.authenticationRepository.hintedPinLength) { add(it + 1) } } @@ -494,7 +549,9 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun hintedPinLength_withAutoConfirmPinTooLong_isNull() = testScope.runTest { val hintedPinLength by collectLastValue(underTest.hintedPinLength) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) utils.authenticationRepository.overrideCredential( buildList { repeat(utils.authenticationRepository.hintedPinLength + 1) { add(it + 1) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt index 14fc931522a42..df4d2225f459d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt @@ -19,8 +19,9 @@ package com.android.systemui.bouncer.domain.interactor import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.model.AuthenticationMethodModel import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils @@ -73,6 +74,7 @@ class BouncerInteractorTest : SysuiTestCase() { val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + runCurrent() utils.authenticationRepository.setUnlocked(false) underTest.showOrUnlockDevice() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -104,6 +106,7 @@ class BouncerInteractorTest : SysuiTestCase() { val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + runCurrent() utils.authenticationRepository.setAutoConfirmEnabled(true) utils.authenticationRepository.setUnlocked(false) underTest.showOrUnlockDevice() @@ -140,6 +143,7 @@ class BouncerInteractorTest : SysuiTestCase() { val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + runCurrent() utils.authenticationRepository.setUnlocked(false) underTest.showOrUnlockDevice() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -170,6 +174,7 @@ class BouncerInteractorTest : SysuiTestCase() { utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) + runCurrent() utils.authenticationRepository.setUnlocked(false) underTest.showOrUnlockDevice() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -202,6 +207,7 @@ class BouncerInteractorTest : SysuiTestCase() { utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pattern ) + runCurrent() utils.authenticationRepository.setUnlocked(false) underTest.showOrUnlockDevice() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -214,11 +220,7 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN) // Wrong input. - assertThat( - underTest.authenticate( - listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(1, 2)) - ) - ) + assertThat(underTest.authenticate(listOf(AuthenticationPatternCoordinate(1, 2)))) .isFalse() assertThat(message).isEqualTo(MESSAGE_WRONG_PATTERN) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -248,7 +250,8 @@ class BouncerInteractorTest : SysuiTestCase() { fun showOrUnlockDevice_authMethodNotSecure_switchesToGoneScene() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + utils.authenticationRepository.setLockscreenEnabled(true) utils.authenticationRepository.setUnlocked(false) underTest.showOrUnlockDevice() @@ -264,6 +267,7 @@ class BouncerInteractorTest : SysuiTestCase() { utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) + runCurrent() utils.authenticationRepository.setUnlocked(false) val customMessage = "Hello there!" diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt index 2cc949326fa0f..7af8a04254020 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt @@ -18,19 +18,17 @@ package com.android.systemui.bouncer.ui.viewmodel import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.model.AuthenticationMethodModel 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.scene.SceneTestUtils import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow 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 AuthMethodBouncerViewModelTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt index 0df0a17931f47..2c96bcc9dd337 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt @@ -18,8 +18,9 @@ package com.android.systemui.bouncer.ui.viewmodel import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils import com.google.common.truth.Truth.assertThat @@ -55,6 +56,7 @@ class BouncerViewModelTest : SysuiTestCase() { private val underTest = utils.bouncerViewModel( bouncerInteractor = bouncerInteractor, + authenticationInteractor = authenticationInteractor, ) @Test @@ -86,7 +88,8 @@ class BouncerViewModelTest : SysuiTestCase() { @Test fun authMethod_reusesInstances() = testScope.runTest { - val seen = mutableMapOf() + val seen = + mutableMapOf() val authMethodViewModel: AuthMethodBouncerViewModel? by collectLastValue(underTest.authMethod) // First pass, populate our "seen" map: @@ -105,7 +108,7 @@ class BouncerViewModelTest : SysuiTestCase() { @Test fun authMethodsToTest_returnsCompleteSampleOfAllAuthMethodTypes() { assertThat(authMethodsToTest().map { it::class }.toSet()) - .isEqualTo(AuthenticationMethodModel::class.sealedSubclasses.toSet()) + .isEqualTo(DomainLayerAuthenticationMethodModel::class.sealedSubclasses.toSet()) } @Test @@ -113,7 +116,9 @@ class BouncerViewModelTest : SysuiTestCase() { testScope.runTest { val message by collectLastValue(underTest.message) val throttling by collectLastValue(bouncerInteractor.throttling) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) assertThat(message?.isUpdateAnimated).isTrue() repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { @@ -136,7 +141,9 @@ class BouncerViewModelTest : SysuiTestCase() { } ) val throttling by collectLastValue(bouncerInteractor.throttling) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) assertThat(isInputEnabled).isTrue() repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { @@ -153,7 +160,9 @@ class BouncerViewModelTest : SysuiTestCase() { fun throttlingDialogMessage() = testScope.runTest { val throttlingDialogMessage by collectLastValue(underTest.throttlingDialogMessage) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { // Wrong PIN. @@ -166,13 +175,32 @@ class BouncerViewModelTest : SysuiTestCase() { assertThat(throttlingDialogMessage).isNull() } - private fun authMethodsToTest(): List { + private fun authMethodsToTest(): List { return listOf( - AuthenticationMethodModel.None, - AuthenticationMethodModel.Swipe, - AuthenticationMethodModel.Pin, - AuthenticationMethodModel.Password, - AuthenticationMethodModel.Pattern, + DomainLayerAuthenticationMethodModel.None, + DomainLayerAuthenticationMethodModel.Swipe, + DomainLayerAuthenticationMethodModel.Pin, + DomainLayerAuthenticationMethodModel.Password, + DomainLayerAuthenticationMethodModel.Pattern, ) } + + private fun FakeAuthenticationRepository.setAuthenticationMethod( + model: DomainLayerAuthenticationMethodModel, + ) { + setAuthenticationMethod( + when (model) { + is DomainLayerAuthenticationMethodModel.None, + is DomainLayerAuthenticationMethodModel.Swipe -> + DataLayerAuthenticationMethodModel.None + is DomainLayerAuthenticationMethodModel.Pin -> + DataLayerAuthenticationMethodModel.Pin + is DomainLayerAuthenticationMethodModel.Password -> + DataLayerAuthenticationMethodModel.Password + is DomainLayerAuthenticationMethodModel.Pattern -> + DataLayerAuthenticationMethodModel.Pattern + } + ) + setLockscreenEnabled(model !is DomainLayerAuthenticationMethodModel.None) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt index 7f8d54c433870..4e9fe8d91da1c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt @@ -19,7 +19,7 @@ package com.android.systemui.bouncer.ui.viewmodel import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.data.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.SceneKey @@ -55,6 +55,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { private val bouncerViewModel = utils.bouncerViewModel( bouncerInteractor = bouncerInteractor, + authenticationInteractor = authenticationInteractor, ) private val underTest = PasswordBouncerViewModel( diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt index 57fcbe595fa6e..000200c606b39 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt @@ -19,8 +19,8 @@ package com.android.systemui.bouncer.ui.viewmodel import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.model.AuthenticationMethodModel 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.scene.SceneTestUtils import com.android.systemui.scene.shared.model.SceneKey @@ -57,6 +57,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { private val bouncerViewModel = utils.bouncerViewModel( bouncerInteractor = bouncerInteractor, + authenticationInteractor = authenticationInteractor, ) private val underTest = PatternBouncerViewModel( diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt index 81c68ed2320f3..4b667c393b62b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt @@ -19,8 +19,8 @@ package com.android.systemui.bouncer.ui.viewmodel import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.model.AuthenticationMethodModel 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.scene.SceneTestUtils import com.android.systemui.scene.shared.model.SceneKey @@ -57,6 +57,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { private val bouncerViewModel = utils.bouncerViewModel( bouncerInteractor = bouncerInteractor, + authenticationInteractor = authenticationInteractor, ) private val underTest = PinBouncerViewModel( 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 deleted file mode 100644 index 86e56bf1e131a..0000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt +++ /dev/null @@ -1,172 +0,0 @@ -/* - * 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.shared.model.AuthenticationMethodModel -import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.scene.SceneTestUtils -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.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 utils = SceneTestUtils(this) - private val testScope = utils.testScope - private val sceneInteractor = utils.sceneInteractor() - private val authenticationInteractor = - utils.authenticationInteractor( - repository = utils.authenticationRepository(), - ) - private val underTest = - utils.lockScreenSceneInteractor( - authenticationInteractor = authenticationInteractor, - bouncerInteractor = - utils.bouncerInteractor( - authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, - ), - ) - - @Test - fun isDeviceLocked() = - testScope.runTest { - val isDeviceLocked by collectLastValue(underTest.isDeviceLocked) - - utils.authenticationRepository.setUnlocked(false) - assertThat(isDeviceLocked).isTrue() - - utils.authenticationRepository.setUnlocked(true) - assertThat(isDeviceLocked).isFalse() - } - - @Test - fun isSwipeToDismissEnabled_deviceLockedAndAuthMethodSwipe_true() = - testScope.runTest { - val isSwipeToDismissEnabled by collectLastValue(underTest.isSwipeToDismissEnabled) - - utils.authenticationRepository.setUnlocked(false) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe) - - assertThat(isSwipeToDismissEnabled).isTrue() - } - - @Test - fun isSwipeToDismissEnabled_deviceUnlockedAndAuthMethodSwipe_false() = - testScope.runTest { - val isSwipeToDismissEnabled by collectLastValue(underTest.isSwipeToDismissEnabled) - - utils.authenticationRepository.setUnlocked(true) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe) - - assertThat(isSwipeToDismissEnabled).isFalse() - } - - @Test - fun dismissLockScreen_deviceLockedWithSecureAuthMethod_switchesToBouncer() = - testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) - utils.authenticationRepository.setUnlocked(false) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - 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) - utils.authenticationRepository.setUnlocked(true) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - 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) - utils.authenticationRepository.setUnlocked(false) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) - - underTest.dismissLockscreen() - - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) - } - - @Test - fun switchFromLockScreenToGone_authMethodNotSwipe_doesNotUnlockDevice() = - testScope.runTest { - val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Lockscreen), "reason") - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - assertThat(isUnlocked).isFalse() - - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone), "reason") - - assertThat(isUnlocked).isFalse() - } - - @Test - fun switchFromNonLockScreenToGone_authMethodSwipe_doesNotUnlockDevice() = - testScope.runTest { - val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked) - runCurrent() - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Shade), "reason") - runCurrent() - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe) - runCurrent() - assertThat(isUnlocked).isFalse() - - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone), "reason") - - assertThat(isUnlocked).isFalse() - } - - @Test - fun authMethodChangedToNone_notOnLockScreenScene_doesNotDismissLockScreen() = - testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe) - runCurrent() - sceneInteractor.setCurrentScene(SceneModel(SceneKey.QuickSettings), "reason") - runCurrent() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.QuickSettings)) - - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.QuickSettings)) - } -} 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 index 63ee240fd2c6f..834b9c5266699 100644 --- 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 @@ -19,7 +19,7 @@ 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.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.data.model.AuthenticationMethodModel import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils @@ -49,14 +49,11 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { private val underTest = LockscreenSceneViewModel( applicationScope = testScope.backgroundScope, - interactor = - utils.lockScreenSceneInteractor( + authenticationInteractor = authenticationInteractor, + bouncerInteractor = + utils.bouncerInteractor( authenticationInteractor = authenticationInteractor, - bouncerInteractor = - utils.bouncerInteractor( - authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, - ), + sceneInteractor = sceneInteractor, ), ) @@ -87,17 +84,18 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { } @Test - fun upTransitionSceneKey_swipeToUnlockedEnabled_gone() = + fun upTransitionSceneKey_swipeToUnlockEnabled_gone() = testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + utils.authenticationRepository.setLockscreenEnabled(true) utils.authenticationRepository.setUnlocked(false) assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone) } @Test - fun upTransitionSceneKey_swipeToUnlockedNotEnabled_bouncer() = + fun upTransitionSceneKey_swipeToUnlockNotEnabled_bouncer() = testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index ee42a70112648..bb365d05e9e2d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt @@ -18,7 +18,7 @@ package com.android.systemui.qs.ui.viewmodel import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.data.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.SceneKey @@ -46,14 +46,10 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { private val underTest = QuickSettingsSceneViewModel( - lockscreenSceneInteractor = - utils.lockScreenSceneInteractor( + bouncerInteractor = + utils.bouncerInteractor( authenticationInteractor = authenticationInteractor, - bouncerInteractor = - utils.bouncerInteractor( - authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, - ), + sceneInteractor = sceneInteractor, ), ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt index 8739b28c940ec..d9301604c67fb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt @@ -18,7 +18,7 @@ package com.android.systemui.shade.ui.viewmodel import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.data.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.SceneKey @@ -47,14 +47,11 @@ class ShadeSceneViewModelTest : SysuiTestCase() { private val underTest = ShadeSceneViewModel( applicationScope = testScope.backgroundScope, - lockscreenSceneInteractor = - utils.lockScreenSceneInteractor( + authenticationInteractor = authenticationInteractor, + bouncerInteractor = + utils.bouncerInteractor( authenticationInteractor = authenticationInteractor, - bouncerInteractor = - utils.bouncerInteractor( - authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, - ), + sceneInteractor = sceneInteractor, ), ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt index c2e1ac70af80d..7c98df6c6317d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt @@ -20,7 +20,8 @@ import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockPatternView import com.android.internal.widget.LockscreenCredential import com.android.keyguard.KeyguardSecurityModel.SecurityMode -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.data.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.authentication.shared.model.AuthenticationResultModel import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import kotlinx.coroutines.flow.MutableStateFlow @@ -47,7 +48,7 @@ class FakeAuthenticationRepository( private val _authenticationMethod = MutableStateFlow(DEFAULT_AUTHENTICATION_METHOD) - val authenticationMethod: StateFlow = + override val authenticationMethod: StateFlow = _authenticationMethod.asStateFlow() private var isLockscreenEnabled = true @@ -154,13 +155,13 @@ class FakeAuthenticationRepository( val DEFAULT_AUTHENTICATION_METHOD = AuthenticationMethodModel.Pin val PATTERN = listOf( - AuthenticationMethodModel.Pattern.PatternCoordinate(2, 0), - AuthenticationMethodModel.Pattern.PatternCoordinate(2, 1), - AuthenticationMethodModel.Pattern.PatternCoordinate(2, 2), - AuthenticationMethodModel.Pattern.PatternCoordinate(1, 1), - AuthenticationMethodModel.Pattern.PatternCoordinate(0, 0), - AuthenticationMethodModel.Pattern.PatternCoordinate(0, 1), - AuthenticationMethodModel.Pattern.PatternCoordinate(0, 2), + AuthenticationPatternCoordinate(2, 0), + AuthenticationPatternCoordinate(2, 1), + AuthenticationPatternCoordinate(2, 2), + AuthenticationPatternCoordinate(1, 1), + AuthenticationPatternCoordinate(0, 0), + AuthenticationPatternCoordinate(0, 1), + AuthenticationPatternCoordinate(0, 2), ) const val MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING = 5 const val THROTTLE_DURATION_MS = 30000 @@ -172,7 +173,6 @@ class FakeAuthenticationRepository( is AuthenticationMethodModel.Pin -> SecurityMode.PIN is AuthenticationMethodModel.Password -> SecurityMode.Password is AuthenticationMethodModel.Pattern -> SecurityMode.Pattern - is AuthenticationMethodModel.Swipe, is AuthenticationMethodModel.None -> SecurityMode.None } } @@ -208,8 +208,7 @@ class FakeAuthenticationRepository( } } - private fun List.toCells(): - List { + private fun List.toCells(): List { return map { coordinate -> LockPatternView.Cell.of(coordinate.y, coordinate.x) } } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt index 6cffb669d466f..62087df8c238f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt @@ -32,7 +32,6 @@ import com.android.systemui.keyguard.data.repository.FakeCommandQueue import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor import com.android.systemui.keyguard.shared.model.WakeSleepReason import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.keyguard.shared.model.WakefulnessState @@ -176,23 +175,14 @@ class SceneTestUtils( fun bouncerViewModel( bouncerInteractor: BouncerInteractor, + authenticationInteractor: AuthenticationInteractor, ): BouncerViewModel { return BouncerViewModel( applicationContext = context, applicationScope = applicationScope(), - interactor = bouncerInteractor, - featureFlags = featureFlags, - ) - } - - fun lockScreenSceneInteractor( - authenticationInteractor: AuthenticationInteractor, - bouncerInteractor: BouncerInteractor, - ): LockscreenSceneInteractor { - return LockscreenSceneInteractor( - applicationScope = applicationScope(), - authenticationInteractor = authenticationInteractor, bouncerInteractor = bouncerInteractor, + authenticationInteractor = authenticationInteractor, + featureFlags = featureFlags, ) }