From db7568e3c467c1fcf82993012399070cadf12f34 Mon Sep 17 00:00:00 2001 From: Alejandro Nijamkin Date: Fri, 30 Jun 2023 17:47:43 -0700 Subject: [PATCH] [flexiglass] Hooks up real app state and logic in keyguard (3) - Connects the unlock logic to Flexiglass to let it unlock the device. Bug: 280883900 Test: Unit tests still pass. Test: manually verified that unlocking the device through the Flexiglass bouncer actually unlocks the actual device. Test: manually verified that locking the device using the power button (with "instantly lock device" setting both on and off) properly locks the device and moves back to the Lockscreen scene that shows the lock icon as locked. Test: verified that above with no lock screen, swipe method, PIN, pattern, and password methods. Change-Id: I1cfb71ab31cf2ddda54c8009656a58b3f64e6fa6 --- .../KeyguardSecurityContainerController.java | 43 +- .../repository/AuthenticationRepository.kt | 219 ++++- .../interactor/AuthenticationInteractor.kt | 234 +++-- .../shared/model/AuthenticationMethodModel.kt | 27 +- .../shared/model/AuthenticationResultModel.kt | 25 + .../model/AuthenticationThrottlingModel.kt | 32 + .../data/repository/BouncerRepository.kt | 9 - .../domain/interactor/BouncerInteractor.kt | 99 ++- .../model/AuthenticationThrottledModel.kt | 30 - .../bouncer/ui/viewmodel/BouncerViewModel.kt | 35 +- .../ui/viewmodel/PatternBouncerViewModel.kt | 15 +- .../ui/viewmodel/PinBouncerViewModel.kt | 65 +- .../repository/SceneContainerRepository.kt | 36 + .../domain/interactor/SceneInteractor.kt | 12 + .../shared/model/SceneTransitionModel.kt | 25 + .../user/domain/interactor/UserInteractor.kt | 21 +- ...yguardSecurityContainerControllerTest.java | 732 ---------------- ...KeyguardSecurityContainerControllerTest.kt | 810 ++++++++++++++++++ .../AuthenticationInteractorTest.kt | 327 +++---- .../interactor/BouncerInteractorTest.kt | 129 +-- .../AuthMethodBouncerViewModelTest.kt | 4 +- .../ui/viewmodel/BouncerViewModelTest.kt | 32 +- .../viewmodel/PasswordBouncerViewModelTest.kt | 51 +- .../viewmodel/PatternBouncerViewModelTest.kt | 65 +- .../ui/viewmodel/PinBouncerViewModelTest.kt | 165 ++-- .../LockscreenSceneInteractorTest.kt | 12 +- .../viewmodel/LockscreenSceneViewModelTest.kt | 24 +- .../QuickSettingsSceneViewModelTest.kt | 8 +- .../SceneContainerRepositoryTest.kt | 96 ++- .../domain/interactor/SceneInteractorTest.kt | 46 +- .../viewmodel/SceneContainerViewModelTest.kt | 6 +- .../ui/viewmodel/ShadeSceneViewModelTest.kt | 16 +- .../FakeAuthenticationRepository.kt | 181 +++- .../data/repository/FakeKeyguardRepository.kt | 4 + .../android/systemui/scene/SceneTestUtils.kt | 43 +- 35 files changed, 2205 insertions(+), 1473 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt create mode 100644 packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt delete mode 100644 packages/SystemUI/src/com/android/systemui/bouncer/shared/model/AuthenticationThrottledModel.kt create mode 100644 packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneTransitionModel.kt delete mode 100644 packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java create mode 100644 packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 458ca2b17c6d9..f9523370adb17 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -79,17 +79,25 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteracto import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.scene.domain.interactor.SceneInteractor; +import com.android.systemui.scene.shared.model.SceneContainerNames; +import com.android.systemui.scene.shared.model.SceneKey; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserSwitcherController; +import com.android.systemui.user.domain.interactor.UserInteractor; import com.android.systemui.util.ViewController; +import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.settings.GlobalSettings; import java.io.File; import java.util.Optional; import javax.inject.Inject; +import javax.inject.Provider; + +import kotlinx.coroutines.Job; /** Controller for {@link KeyguardSecurityContainer} */ @KeyguardBouncerScope @@ -378,6 +386,10 @@ public class KeyguardSecurityContainerController extends ViewController mSceneInteractor; + private final Provider mJavaAdapter; + @Nullable private Job mSceneTransitionCollectionJob; @Inject public KeyguardSecurityContainerController(KeyguardSecurityContainer view, @@ -402,7 +414,10 @@ public class KeyguardSecurityContainerController extends ViewController javaAdapter, + UserInteractor userInteractor, + Provider sceneInteractor ) { super(view); mLockPatternUtils = lockPatternUtils; @@ -429,6 +444,9 @@ public class KeyguardSecurityContainerController extends ViewController { + if (sceneTransitionModel != null + && sceneTransitionModel.getFrom() == SceneKey.Bouncer.INSTANCE + && sceneTransitionModel.getTo() == SceneKey.Gone.INSTANCE) { + final int selectedUserId = mUserInteractor.getSelectedUserId(); + showNextSecurityScreenOrFinish( + /* authenticated= */ true, + selectedUserId, + /* bypassSecondaryLockScreen= */ true, + mSecurityModel.getSecurityMode(selectedUserId)); + } + }); + } } @Override @@ -459,6 +495,11 @@ public class KeyguardSecurityContainerController extends ViewController - /** - * Number of consecutively failed authentication attempts. This resets to `0` when - * authentication succeeds. - */ - val failedAuthenticationAttempts: StateFlow + /** Whether the auto confirm feature is enabled for the currently-selected user. */ + val isAutoConfirmEnabled: StateFlow + + /** The length of the PIN for which we should show a hint. */ + val hintedPinLength: Int + + /** Whether the pattern should be visible for the currently-selected user. */ + val isPatternVisible: StateFlow + + /** The current throttling state, as cached via [setThrottling]. */ + val throttling: StateFlow /** * Returns the currently-configured authentication method. This determines how the @@ -69,11 +89,48 @@ interface AuthenticationRepository { */ suspend fun getAuthenticationMethod(): AuthenticationMethodModel + /** Returns the length of the PIN or `0` if the current auth method is not PIN. */ + suspend fun getPinLength(): Int + + /** + * Returns whether the lockscreen is enabled. + * + * When the lockscreen is not enabled, it shouldn't show in cases when the authentication method + * is considered not secure (for example, "swipe" is considered to be "none"). + */ + suspend fun isLockscreenEnabled(): Boolean + /** See [isBypassEnabled]. */ fun setBypassEnabled(isBypassEnabled: Boolean) - /** See [failedAuthenticationAttempts]. */ - fun setFailedAuthenticationAttempts(failedAuthenticationAttempts: Int) + /** Reports an authentication attempt. */ + suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) + + /** Returns the current number of failed authentication attempts. */ + suspend fun getFailedAuthenticationAttemptCount(): Int + + /** + * Returns the timestamp for when the current throttling will end, allowing the user to attempt + * authentication again. + * + * Note that this is in milliseconds and it matches [SystemClock.elapsedRealtime]. + */ + suspend fun getThrottlingEndTimestamp(): Long + + /** Sets the cached throttling state, updating the [throttling] flow. */ + fun setThrottling(throttlingModel: AuthenticationThrottlingModel) + + /** + * Sets the throttling timeout duration (time during which the user should not be allowed to + * attempt authentication). + */ + suspend fun setThrottleDuration(durationMs: Int) + + /** + * Checks the given [LockscreenCredential] to see if it's correct, returning an + * [AuthenticationResultModel] representing what happened. + */ + suspend fun checkCredential(credential: LockscreenCredential): AuthenticationResultModel } class AuthenticationRepositoryImpl @@ -83,8 +140,8 @@ constructor( private val getSecurityMode: Function, @Background private val backgroundDispatcher: CoroutineDispatcher, private val userRepository: UserRepository, - private val lockPatternUtils: LockPatternUtils, keyguardRepository: KeyguardRepository, + private val lockPatternUtils: LockPatternUtils, ) : AuthenticationRepository { override val isUnlocked: StateFlow = @@ -94,54 +151,140 @@ constructor( initialValue = false, ) + override suspend fun isLockscreenEnabled(): Boolean { + return withContext(backgroundDispatcher) { + val selectedUserId = userRepository.selectedUserId + !lockPatternUtils.isLockPatternEnabled(selectedUserId) + } + } + private val _isBypassEnabled = MutableStateFlow(false) override val isBypassEnabled: StateFlow = _isBypassEnabled.asStateFlow() - private val _failedAuthenticationAttempts = MutableStateFlow(0) - override val failedAuthenticationAttempts: StateFlow = - _failedAuthenticationAttempts.asStateFlow() + override val isAutoConfirmEnabled: StateFlow = + userRepository.selectedUserInfo + .map { it.id } + .flatMapLatest { userId -> + flow { emit(lockPatternUtils.isAutoPinConfirmEnabled(userId)) } + .flowOn(backgroundDispatcher) + } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = true, + ) + + override val hintedPinLength: Int = LockPatternUtils.MIN_AUTO_PIN_REQUIREMENT_LENGTH + + override val isPatternVisible: StateFlow = + userRepository.selectedUserInfo + .map { it.id } + .flatMapLatest { userId -> + flow { emit(lockPatternUtils.isVisiblePatternEnabled(userId)) } + .flowOn(backgroundDispatcher) + } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = true, + ) + + private val _throttling = MutableStateFlow(AuthenticationThrottlingModel()) + override val throttling: StateFlow = _throttling.asStateFlow() + + private val UserRepository.selectedUserId: Int + get() = getSelectedUserInfo().id override suspend fun getAuthenticationMethod(): AuthenticationMethodModel { return withContext(backgroundDispatcher) { - val selectedUserId = userRepository.getSelectedUserInfo().id + val selectedUserId = userRepository.selectedUserId when (getSecurityMode.apply(selectedUserId)) { KeyguardSecurityModel.SecurityMode.PIN, - KeyguardSecurityModel.SecurityMode.SimPin -> - AuthenticationMethodModel.Pin( - code = listOf(1, 2, 3, 4), // TODO(b/280883900): remove this - autoConfirm = lockPatternUtils.isAutoPinConfirmEnabled(selectedUserId), - ) - KeyguardSecurityModel.SecurityMode.Password, - KeyguardSecurityModel.SecurityMode.SimPuk -> - AuthenticationMethodModel.Password( - password = "password", // TODO(b/280883900): remove this - ) - KeyguardSecurityModel.SecurityMode.Pattern -> - AuthenticationMethodModel.Pattern( - coordinates = - 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), - ), // TODO(b/280883900): remove this - ) + 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!") - null -> error("Invalid security is null!") } } } + override suspend fun getPinLength(): Int { + return withContext(backgroundDispatcher) { + val selectedUserId = userRepository.selectedUserId + lockPatternUtils.getPinLength(selectedUserId) + } + } + override fun setBypassEnabled(isBypassEnabled: Boolean) { _isBypassEnabled.value = isBypassEnabled } - override fun setFailedAuthenticationAttempts(failedAuthenticationAttempts: Int) { - _failedAuthenticationAttempts.value = failedAuthenticationAttempts + override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) { + val selectedUserId = userRepository.selectedUserId + withContext(backgroundDispatcher) { + if (isSuccessful) { + lockPatternUtils.reportSuccessfulPasswordAttempt(selectedUserId) + } else { + lockPatternUtils.reportFailedPasswordAttempt(selectedUserId) + } + } + } + + override suspend fun getFailedAuthenticationAttemptCount(): Int { + return withContext(backgroundDispatcher) { + val selectedUserId = userRepository.selectedUserId + lockPatternUtils.getCurrentFailedPasswordAttempts(selectedUserId) + } + } + + override suspend fun getThrottlingEndTimestamp(): Long { + return withContext(backgroundDispatcher) { + val selectedUserId = userRepository.selectedUserId + lockPatternUtils.getLockoutAttemptDeadline(selectedUserId) + } + } + + override fun setThrottling(throttlingModel: AuthenticationThrottlingModel) { + _throttling.value = throttlingModel + } + + override suspend fun setThrottleDuration(durationMs: Int) { + withContext(backgroundDispatcher) { + lockPatternUtils.setLockoutAttemptDeadline( + userRepository.selectedUserId, + durationMs, + ) + } + } + + override suspend fun checkCredential( + credential: LockscreenCredential + ): AuthenticationResultModel { + return suspendCoroutine { continuation -> + LockPatternChecker.checkCredential( + lockPatternUtils, + credential, + userRepository.selectedUserId, + object : LockPatternChecker.OnCheckCallback { + override fun onChecked(matched: Boolean, throttleTimeoutMs: Int) { + continuation.resume( + AuthenticationResultModel( + isSuccessful = matched, + throttleDurationMs = throttleTimeoutMs, + ) + ) + } + + override fun onCancelled() { + continuation.resume(AuthenticationResultModel(isSuccessful = false)) + } + + override fun onEarlyMatched() = Unit + } + ) + } } } 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 15e579d6d72eb..82674bff64337 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 @@ -16,25 +16,43 @@ package com.android.systemui.authentication.domain.interactor -import android.app.admin.DevicePolicyManager +import com.android.internal.widget.LockPatternView +import com.android.internal.widget.LockscreenCredential import com.android.systemui.authentication.data.repository.AuthenticationRepository import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.user.data.repository.UserRepository +import com.android.systemui.util.time.SystemClock import javax.inject.Inject +import kotlin.math.max +import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.async +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext /** Hosts application business logic related to authentication. */ @SysUISingleton class AuthenticationInteractor @Inject constructor( - @Application applicationScope: CoroutineScope, + @Application private val applicationScope: CoroutineScope, private val repository: AuthenticationRepository, + @Background private val backgroundDispatcher: CoroutineDispatcher, + private val userRepository: UserRepository, + private val clock: SystemClock, ) { /** * Whether the device is unlocked. @@ -67,18 +85,66 @@ constructor( */ val isBypassEnabled: StateFlow = repository.isBypassEnabled + /** The current authentication throttling state, only meaningful if [isThrottled] is `true`. */ + val throttling: StateFlow = repository.throttling + /** - * Number of consecutively failed authentication attempts. This resets to `0` when - * authentication succeeds. + * Whether currently throttled and the user has to wait before being able to try another + * authentication attempt. */ - val failedAuthenticationAttempts: StateFlow = repository.failedAuthenticationAttempts + val isThrottled: StateFlow = + throttling + .map { it.remainingMs > 0 } + .stateIn( + scope = applicationScope, + started = SharingStarted.Eagerly, + initialValue = throttling.value.remainingMs > 0, + ) + + /** The length of the hinted PIN, or `null` if pin length hint should not be shown. */ + val hintedPinLength: StateFlow = + flow { emit(repository.getPinLength()) } + .map { currentPinLength -> + // Hinting is enabled for 6-digit codes only + currentPinLength.takeIf { repository.hintedPinLength == it } + } + .stateIn( + scope = applicationScope, + started = SharingStarted.Eagerly, + initialValue = null, + ) + + /** Whether the auto confirm feature is enabled for the currently-selected user. */ + val isAutoConfirmEnabled: StateFlow = repository.isAutoConfirmEnabled + + /** Whether the pattern should be visible for the currently-selected user. */ + val isPatternVisible: StateFlow = repository.isPatternVisible + + private var throttlingCountdownJob: Job? = null + + init { + applicationScope.launch { + userRepository.selectedUserInfo + .map { it.id } + .distinctUntilChanged() + .collect { onSelectedUserChanged() } + } + } /** * 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 repository.getAuthenticationMethod() + 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 + } } /** @@ -104,39 +170,52 @@ constructor( * authentication failed, `null` if the check was not performed. */ suspend fun authenticate(input: List, tryAutoConfirm: Boolean = false): Boolean? { - val authMethod = getAuthenticationMethod() - if (tryAutoConfirm) { - if ((authMethod as? AuthenticationMethodModel.Pin)?.autoConfirm != true) { - // Do not attempt to authenticate unless the PIN lock is set to auto-confirm. - return null - } - - if (input.size < authMethod.code.size) { - // Do not attempt to authenticate if the PIN has not yet the required amount of - // digits. This intentionally only skip for shorter PINs; if the PIN is longer, the - // layer above might have throttled this check, and the PIN should be rejected via - // the auth code below. - return null - } + if (input.isEmpty()) { + throw IllegalArgumentException("Input was empty!") } - val isSuccessful = - when (authMethod) { - is AuthenticationMethodModel.Pin -> input.asCode() == authMethod.code - is AuthenticationMethodModel.Password -> input.asPassword() == authMethod.password - is AuthenticationMethodModel.Pattern -> input.asPattern() == authMethod.coordinates - else -> true + val skipCheck = + when { + // We're being throttled, the UI layer should not have called this; skip the + // attempt. + isThrottled.value -> true + // Auto-confirm attempt when the feature is not enabled; skip the attempt. + tryAutoConfirm && !isAutoConfirmEnabled.value -> true + // Auto-confirm should skip the attempt if the pin entered is too short. + tryAutoConfirm && input.size < repository.getPinLength() -> true + else -> false } + if (skipCheck) { + return null + } - if (isSuccessful) { - repository.setFailedAuthenticationAttempts(0) - } else { - repository.setFailedAuthenticationAttempts( - repository.failedAuthenticationAttempts.value + 1 + // Attempt to authenticate: + val authMethod = getAuthenticationMethod() + val credential = authMethod.createCredential(input) ?: return null + val authenticationResult = repository.checkCredential(credential) + credential.zeroize() + + if (authenticationResult.isSuccessful || !tryAutoConfirm) { + repository.reportAuthenticationAttempt( + isSuccessful = authenticationResult.isSuccessful, ) } - return isSuccessful + // Check if we need to throttle and, if so, kick off the throttle countdown: + if (!authenticationResult.isSuccessful && authenticationResult.throttleDurationMs > 0) { + repository.setThrottleDuration( + durationMs = authenticationResult.throttleDurationMs, + ) + startThrottlingCountdown() + } + + if (authenticationResult.isSuccessful) { + // Since authentication succeeded, we should refresh throttling to make sure that our + // state is completely reflecting the upstream source of truth. + refreshThrottling() + } + + return authenticationResult.isSuccessful } /** See [isBypassEnabled]. */ @@ -144,44 +223,67 @@ constructor( repository.setBypassEnabled(!repository.isBypassEnabled.value) } - companion object { - /** - * Returns a PIN code from the given list. It's assumed the given list elements are all - * [Int] in the range [0-9]. - */ - private fun List.asCode(): List? { - if (isEmpty() || size > DevicePolicyManager.MAX_PASSWORD_LENGTH) { - return null - } - - return map { - require(it is Int && it in 0..9) { - "Pin is required to be Int in range [0..9], but got $it" + /** Starts refreshing the throttling state every second. */ + private suspend fun startThrottlingCountdown() { + cancelCountdown() + throttlingCountdownJob = + applicationScope.launch { + while (refreshThrottling() > 0) { + delay(1.seconds.inWholeMilliseconds) } - it } - } + } - /** - * Returns a password from the given list. It's assumed the given list elements are all - * [Char]. - */ - private fun List.asPassword(): String { - val anyList = this - return buildString { anyList.forEach { append(it as Char) } } - } + /** Cancels any throttling state countdown started in [startThrottlingCountdown]. */ + private fun cancelCountdown() { + throttlingCountdownJob?.cancel() + throttlingCountdownJob = null + } - /** - * Returns a list of [AuthenticationMethodModel.Pattern.PatternCoordinate] from the given - * list. It's assumed the given list elements are all - * [AuthenticationMethodModel.Pattern.PatternCoordinate]. - */ - private fun List.asPattern(): - List { - val anyList = this - return buildList { - anyList.forEach { add(it as AuthenticationMethodModel.Pattern.PatternCoordinate) } - } + /** Notifies that the currently-selected user has changed. */ + private suspend fun onSelectedUserChanged() { + cancelCountdown() + if (refreshThrottling() > 0) { + startThrottlingCountdown() + } + } + + /** + * Refreshes the throttling state, hydrating the repository with the latest state. + * + * @return The remaining time for the current throttling countdown, in milliseconds or `0` if + * not being throttled. + */ + private suspend fun refreshThrottling(): Long { + return withContext(backgroundDispatcher) { + val failedAttemptCount = async { repository.getFailedAuthenticationAttemptCount() } + val deadline = async { repository.getThrottlingEndTimestamp() } + val remainingMs = max(0, deadline.await() - clock.elapsedRealtime()) + repository.setThrottling( + AuthenticationThrottlingModel( + failedAttemptCount = failedAttemptCount.await(), + remainingMs = remainingMs.toInt(), + ), + ) + remainingMs + } + } + + private fun AuthenticationMethodModel.createCredential( + input: List + ): LockscreenCredential? { + return when (this) { + is AuthenticationMethodModel.Pin -> + LockscreenCredential.createPin(input.joinToString("")) + is AuthenticationMethodModel.Password -> + LockscreenCredential.createPassword(input.joinToString("")) + is AuthenticationMethodModel.Pattern -> + LockscreenCredential.createPattern( + input + .map { it as AuthenticationMethodModel.Pattern.PatternCoordinate } + .map { LockPatternView.Cell.of(it.y, it.x) } + ) + else -> null } } } diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt index 1016b6b9a99e6..97c6697f10a5b 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt @@ -16,8 +16,6 @@ package com.android.systemui.authentication.shared.model -import androidx.annotation.VisibleForTesting - /** Enumerates all known authentication methods. */ sealed class AuthenticationMethodModel( /** @@ -34,30 +32,11 @@ sealed class AuthenticationMethodModel( /** The most basic authentication method. The lock screen can be swiped away when displayed. */ object Swipe : AuthenticationMethodModel(isSecure = false) - /** - * Authentication method using a PIN. - * - * In practice, a pin is restricted to 16 decimal digits , see - * [android.app.admin.DevicePolicyManager.MAX_PASSWORD_LENGTH] - */ - data class Pin(val code: List, val autoConfirm: Boolean) : - AuthenticationMethodModel(isSecure = true) { + object Pin : AuthenticationMethodModel(isSecure = true) - /** Convenience constructor for tests only. */ - @VisibleForTesting - constructor( - code: Long, - autoConfirm: Boolean = false - ) : this(code.toString(10).map { it - '0' }, autoConfirm) {} - } - - data class Password(val password: String) : AuthenticationMethodModel(isSecure = true) - - data class Pattern( - val coordinates: List, - val isPatternVisible: Boolean = true, - ) : AuthenticationMethodModel(isSecure = true) { + object Password : AuthenticationMethodModel(isSecure = true) + object Pattern : AuthenticationMethodModel(isSecure = true) { data class PatternCoordinate( val x: Int, val y: Int, diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt new file mode 100644 index 0000000000000..f2a3e74700db9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt @@ -0,0 +1,25 @@ +/* + * 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 + +/** Models the result of an authentication attempt. */ +data class AuthenticationResultModel( + /** Whether authentication was successful. */ + val isSuccessful: Boolean = false, + /** If [isSuccessful] is `false`, how long the user must wait before trying again. */ + val throttleDurationMs: Int = 0, +) diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt new file mode 100644 index 0000000000000..d0d398e318596 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt @@ -0,0 +1,32 @@ +/* + * 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 + +/** Models a state for throttling the next authentication attempt. */ +data class AuthenticationThrottlingModel( + + /** Number of failed authentication attempts so far. If not throttling this will be `0`. */ + val failedAttemptCount: Int = 0, + + /** + * Remaining amount of time, in milliseconds, before another authentication attempt can be done. + * If not throttling this will be `0`. + * + * This number is changed throughout the timeout. + */ + val remainingMs: Int = 0, +) diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt index ff896fa51350d..943216ebd0d64 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt @@ -16,7 +16,6 @@ package com.android.systemui.bouncer.data.repository -import com.android.systemui.bouncer.shared.model.AuthenticationThrottledModel import com.android.systemui.dagger.SysUISingleton import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow @@ -30,15 +29,7 @@ class BouncerRepository @Inject constructor() { /** The user-facing message to show in the bouncer. */ val message: StateFlow = _message.asStateFlow() - private val _throttling = MutableStateFlow(null) - /** The current authentication throttling state. If `null`, there's no throttling. */ - val throttling: StateFlow = _throttling.asStateFlow() - fun setMessage(message: String?) { _message.value = message } - - fun setThrottling(throttling: AuthenticationThrottledModel?) { - _throttling.value = throttling - } } 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 5dd24b27d19c8..2513c81c17f8d 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,24 +14,27 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.bouncer.domain.interactor import android.content.Context -import androidx.annotation.VisibleForTesting 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.shared.model.AuthenticationThrottlingModel import com.android.systemui.bouncer.data.repository.BouncerRepository -import com.android.systemui.bouncer.shared.model.AuthenticationThrottledModel 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 kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.delay +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine @@ -57,9 +60,10 @@ constructor( val message: StateFlow = combine( repository.message, - repository.throttling, - ) { message, throttling -> - messageOrThrottlingMessage(message, throttling) + authenticationInteractor.isThrottled, + authenticationInteractor.throttling, + ) { message, isThrottled, throttling -> + messageOrThrottlingMessage(message, isThrottled, throttling) } .stateIn( scope = applicationScope, @@ -67,12 +71,28 @@ constructor( initialValue = messageOrThrottlingMessage( repository.message.value, - repository.throttling.value, + authenticationInteractor.isThrottled.value, + authenticationInteractor.throttling.value, ) ) - /** The current authentication throttling state. If `null`, there's no throttling. */ - val throttling: StateFlow = repository.throttling + /** The current authentication throttling state, only meaningful if [isThrottled] is `true`. */ + val throttling: StateFlow = authenticationInteractor.throttling + + /** + * Whether currently throttled and the user has to wait before being able to try another + * authentication attempt. + */ + val isThrottled: StateFlow = authenticationInteractor.isThrottled + + /** Whether the auto confirm feature is enabled for the currently-selected user. */ + val isAutoConfirmEnabled: StateFlow = authenticationInteractor.isAutoConfirmEnabled + + /** The length of the PIN for which we should show a hint. */ + val hintedPinLength: StateFlow = authenticationInteractor.hintedPinLength + + /** Whether the pattern should be visible for the currently-selected user. */ + val isPatternVisible: StateFlow = authenticationInteractor.isPatternVisible init { // UNLOCKING SHOWS Gone. @@ -98,6 +118,15 @@ constructor( } } } + + // Clear the message if moved from throttling to no-longer throttling. + applicationScope.launch { + isThrottled.pairwise().collect { (wasThrottled, currentlyThrottled) -> + if (wasThrottled && !currentlyThrottled) { + clearMessage() + } + } + } } /** @@ -168,41 +197,16 @@ constructor( input: List, tryAutoConfirm: Boolean = false, ): Boolean? { - if (repository.throttling.value != null) { - return false - } - val isAuthenticated = authenticationInteractor.authenticate(input, tryAutoConfirm) ?: return null - val failedAttempts = authenticationInteractor.failedAuthenticationAttempts.value - when { - isAuthenticated -> { - repository.setThrottling(null) - sceneInteractor.setCurrentScene( - containerName = containerName, - scene = SceneModel(SceneKey.Gone), - ) - } - failedAttempts >= THROTTLE_AGGRESSIVELY_AFTER || failedAttempts % THROTTLE_EVERY == 0 -> - applicationScope.launch { - var remainingDurationSec = THROTTLE_DURATION_SEC - while (remainingDurationSec > 0) { - repository.setThrottling( - AuthenticationThrottledModel( - failedAttemptCount = failedAttempts, - totalDurationSec = THROTTLE_DURATION_SEC, - remainingDurationSec = remainingDurationSec, - ) - ) - remainingDurationSec-- - delay(1000) - } - - repository.setThrottling(null) - clearMessage() - } - else -> repository.setMessage(errorMessage(getAuthenticationMethod())) + if (isAuthenticated) { + sceneInteractor.setCurrentScene( + containerName = containerName, + scene = SceneModel(SceneKey.Gone), + ) + } else { + repository.setMessage(errorMessage(getAuthenticationMethod())) } return isAuthenticated @@ -233,13 +237,14 @@ constructor( private fun messageOrThrottlingMessage( message: String?, - throttling: AuthenticationThrottledModel?, + isThrottled: Boolean, + throttlingModel: AuthenticationThrottlingModel, ): String { return when { - throttling != null -> + isThrottled -> applicationContext.getString( com.android.internal.R.string.lockscreen_too_many_failed_attempts_countdown, - throttling.remainingDurationSec, + throttlingModel.remainingMs.milliseconds.inWholeSeconds, ) message != null -> message else -> "" @@ -252,10 +257,4 @@ constructor( containerName: String, ): BouncerInteractor } - - companion object { - @VisibleForTesting const val THROTTLE_DURATION_SEC = 30 - @VisibleForTesting const val THROTTLE_AGGRESSIVELY_AFTER = 15 - @VisibleForTesting const val THROTTLE_EVERY = 5 - } } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/AuthenticationThrottledModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/AuthenticationThrottledModel.kt deleted file mode 100644 index cbea635f6b136..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/AuthenticationThrottledModel.kt +++ /dev/null @@ -1,30 +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.bouncer.shared.model - -/** - * Models application state for when further authentication attempts are being throttled due to too - * many consecutive failed authentication attempts. - */ -data class AuthenticationThrottledModel( - /** Total number of failed attempts so far. */ - val failedAttemptCount: Int, - /** Total amount of time the user has to wait before attempting again. */ - val totalDurationSec: Int, - /** Remaining amount of time the user has to wait before attempting again. */ - val remainingDurationSec: Int, -) 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 db6ca0bda0f64..5425022e11b78 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,19 +14,22 @@ * 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.bouncer.domain.interactor.BouncerInteractor -import com.android.systemui.bouncer.shared.model.AuthenticationThrottledModel import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.util.kotlin.pairwise import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +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 @@ -51,12 +54,12 @@ constructor( private val interactor: BouncerInteractor = interactorFactory.create(containerName) private val isInputEnabled: StateFlow = - interactor.throttling - .map { it == null } + interactor.isThrottled + .map { !it } .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), - initialValue = interactor.throttling.value == null, + initialValue = !interactor.isThrottled.value, ) private val pin: PinBouncerViewModel by lazy { @@ -115,9 +118,9 @@ constructor( val message: StateFlow = combine( interactor.message, - interactor.throttling, - ) { message, throttling -> - toMessageViewModel(message, throttling) + interactor.isThrottled, + ) { message, isThrottled -> + toMessageViewModel(message, isThrottled) } .stateIn( scope = applicationScope, @@ -125,7 +128,7 @@ constructor( initialValue = toMessageViewModel( message = interactor.message.value, - throttling = interactor.throttling.value, + isThrottled = interactor.isThrottled.value, ), ) @@ -143,9 +146,9 @@ constructor( init { applicationScope.launch { - interactor.throttling - .map { model -> - model?.let { + interactor.isThrottled + .map { isThrottled -> + if (isThrottled) { when (interactor.getAuthenticationMethod()) { is AuthenticationMethodModel.Pin -> R.string.kg_too_many_failed_pin_attempts_dialog_message @@ -157,10 +160,12 @@ constructor( }?.let { stringResourceId -> applicationContext.getString( stringResourceId, - model.failedAttemptCount, - model.totalDurationSec, + interactor.throttling.value.failedAttemptCount, + ceil(interactor.throttling.value.remainingMs / 1000f).toInt(), ) } + } else { + null } } .distinctUntilChanged() @@ -184,11 +189,11 @@ constructor( private fun toMessageViewModel( message: String?, - throttling: AuthenticationThrottledModel?, + isThrottled: Boolean, ): MessageViewModel { return MessageViewModel( text = message ?: "", - isUpdateAnimated = throttling == null, + isUpdateAnimated = !isThrottled, ) } 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 5efa6f073f7ac..4be539d9396d1 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 @@ -29,7 +29,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @@ -70,19 +69,7 @@ class PatternBouncerViewModel( val dots: StateFlow> = _dots.asStateFlow() /** Whether the pattern itself should be rendered visibly. */ - val isPatternVisible: StateFlow = - flow { - emit(null) - emit(interactor.getAuthenticationMethod()) - } - .map { authMethod -> - (authMethod as? AuthenticationMethodModel.Pattern)?.isPatternVisible ?: false - } - .stateIn( - scope = applicationScope, - started = SharingStarted.Eagerly, - initialValue = false, - ) + val isPatternVisible: StateFlow = interactor.isPatternVisible /** Notifies that the UI has been shown to the user. */ fun onShown() { diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt index 641e863fa3030..1b14acc7fabc9 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt @@ -18,13 +18,12 @@ package com.android.systemui.bouncer.ui.viewmodel import android.content.Context import com.android.keyguard.PinShapeAdapter -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @@ -45,26 +44,18 @@ class PinBouncerViewModel( private val mutablePinEntries = MutableStateFlow>(emptyList()) val pinEntries: StateFlow> = mutablePinEntries - /** The length of the hinted PIN, or `null` if pin length hint should not be shown. */ - val hintedPinLength: StateFlow = - flow { emit(interactor.getAuthenticationMethod()) } - .map { authMethod -> - // Hinting is enabled for 6-digit codes only - autoConfirmPinLength(authMethod).takeIf { it == HINTING_PASSCODE_LENGTH } - } - .stateIn( - scope = applicationScope, - started = SharingStarted.Eagerly, - initialValue = null, - ) + /** The length of the PIN for which we should show a hint. */ + val hintedPinLength: StateFlow = interactor.hintedPinLength /** Appearance of the backspace button. */ val backspaceButtonAppearance: StateFlow = - mutablePinEntries - .map { mutablePinEntries -> + combine( + mutablePinEntries, + interactor.isAutoConfirmEnabled, + ) { mutablePinEntries, isAutoConfirmEnabled -> computeBackspaceButtonAppearance( - interactor.getAuthenticationMethod(), - mutablePinEntries + enteredPin = mutablePinEntries, + isAutoConfirmEnabled = isAutoConfirmEnabled, ) } .stateIn( @@ -75,11 +66,14 @@ class PinBouncerViewModel( /** Appearance of the confirm button. */ val confirmButtonAppearance: StateFlow = - flow { - emit(null) - emit(interactor.getAuthenticationMethod()) + interactor.isAutoConfirmEnabled + .map { + if (it) { + ActionButtonAppearance.Hidden + } else { + ActionButtonAppearance.Shown + } } - .map { authMethod -> computeConfirmButtonAppearance(authMethod) } .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, @@ -134,21 +128,10 @@ class PinBouncerViewModel( } } - private fun isAutoConfirmEnabled(authMethodModel: AuthenticationMethodModel?): Boolean { - return (authMethodModel as? AuthenticationMethodModel.Pin)?.autoConfirm == true - } - - private fun autoConfirmPinLength(authMethodModel: AuthenticationMethodModel?): Int? { - if (!isAutoConfirmEnabled(authMethodModel)) return null - - return (authMethodModel as? AuthenticationMethodModel.Pin)?.code?.size - } - private fun computeBackspaceButtonAppearance( - authMethodModel: AuthenticationMethodModel, - enteredPin: List + enteredPin: List, + isAutoConfirmEnabled: Boolean, ): ActionButtonAppearance { - val isAutoConfirmEnabled = isAutoConfirmEnabled(authMethodModel) val isEmpty = enteredPin.isEmpty() return when { @@ -157,15 +140,6 @@ class PinBouncerViewModel( else -> ActionButtonAppearance.Shown } } - private fun computeConfirmButtonAppearance( - authMethodModel: AuthenticationMethodModel? - ): ActionButtonAppearance { - return if (isAutoConfirmEnabled(authMethodModel)) { - ActionButtonAppearance.Hidden - } else { - ActionButtonAppearance.Shown - } - } } /** Appearance of pin-pad action buttons. */ @@ -178,9 +152,6 @@ enum class ActionButtonAppearance { Shown, } -/** Auto-confirm passcodes of exactly 6 digits show a length hint, see http://shortn/_IXlmSNbDh6 */ -private const val HINTING_PASSCODE_LENGTH = 6 - private var nextSequenceNumber = 1 /** diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt index 1ebeced5fae68..0a86d35b7b623 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt @@ -19,6 +19,7 @@ package com.android.systemui.scene.data.repository import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.scene.shared.model.SceneTransitionModel import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -45,6 +46,9 @@ constructor( containerConfigByName .map { (containerName, _) -> containerName to MutableStateFlow(1f) } .toMap() + private val sceneTransitionByContainerName: + Map> = + containerConfigByName.keys.associateWith { MutableStateFlow(null) } /** * Returns the keys to all scenes in the container with the given name. @@ -70,11 +74,43 @@ constructor( currentSceneByContainerName.setValue(containerName, scene) } + /** Sets the scene transition in the container with the given name. */ + fun setSceneTransition(containerName: String, from: SceneKey, to: SceneKey) { + check(allSceneKeys(containerName).contains(from)) { + """ + Cannot set current scene key to "$from". The container "$containerName" does + not contain a scene with that key. + """ + .trimIndent() + } + check(allSceneKeys(containerName).contains(to)) { + """ + Cannot set current scene key to "$to". The container "$containerName" does + not contain a scene with that key. + """ + .trimIndent() + } + + sceneTransitionByContainerName.setValue( + containerName, + SceneTransitionModel(from = from, to = to) + ) + } + /** The current scene in the container with the given name. */ fun currentScene(containerName: String): StateFlow { return currentSceneByContainerName.mutableOrError(containerName).asStateFlow() } + /** + * Scene transitions as pairs of keys. A new value is emitted exactly once, each time a scene + * transition occurs. The flow begins with a `null` value at first, because the initial scene is + * not something that we transition to from another scene. + */ + fun sceneTransitions(containerName: String): StateFlow { + return sceneTransitionByContainerName.mutableOrError(containerName).asStateFlow() + } + /** Sets whether the container with the given name is visible. */ fun setVisible(containerName: String, isVisible: Boolean) { containerVisibilityByName.setValue(containerName, isVisible) diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt index 1e55975658a5a..c704ea838130e 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt @@ -20,6 +20,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.scene.data.repository.SceneContainerRepository import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.scene.shared.model.SceneTransitionModel import javax.inject.Inject import kotlinx.coroutines.flow.StateFlow @@ -43,7 +44,9 @@ constructor( /** Sets the scene in the container with the given name. */ fun setCurrentScene(containerName: String, scene: SceneModel) { + val currentSceneKey = repository.currentScene(containerName).value.key repository.setCurrentScene(containerName, scene) + repository.setSceneTransition(containerName, from = currentSceneKey, to = scene.key) } /** The current scene in the container with the given name. */ @@ -70,4 +73,13 @@ constructor( fun sceneTransitionProgress(containerName: String): StateFlow { return repository.sceneTransitionProgress(containerName) } + + /** + * Scene transitions as pairs of keys. A new value is emitted exactly once, each time a scene + * transition occurs. The flow begins with a `null` value at first, because the initial scene is + * not something that we transition to from another scene. + */ + fun sceneTransitions(containerName: String): StateFlow { + return repository.sceneTransitions(containerName) + } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneTransitionModel.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneTransitionModel.kt new file mode 100644 index 0000000000000..c8f46a72d64f4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneTransitionModel.kt @@ -0,0 +1,25 @@ +/* + * 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.scene.shared.model + +/** Models a transition between two scenes. */ +data class SceneTransitionModel( + /** The scene we transitioned away from. */ + val from: SceneKey, + /** The scene we transitioned into. */ + val to: SceneKey, +) diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt index a487f538e59ce..ebdeac9b0ceaa 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt @@ -530,6 +530,12 @@ constructor( } } + /** Returns the ID of the currently-selected user. */ + @UserIdInt + fun getSelectedUserId(): Int { + return repository.getSelectedUserInfo().id + } + private fun showDialog(request: ShowDialogRequestModel) { _dialogShowRequests.value = request } @@ -772,17 +778,16 @@ constructor( } // TODO(b/246631653): cache the bitmaps to avoid the background work to fetch them. - val userIcon = withContext(backgroundDispatcher) { - manager.getUserIcon(userId) - ?.let { bitmap -> + val userIcon = + withContext(backgroundDispatcher) { + manager.getUserIcon(userId)?.let { bitmap -> val iconSize = - applicationContext - .resources - .getDimensionPixelSize(R.dimen.bouncer_user_switcher_icon_size) + applicationContext.resources.getDimensionPixelSize( + R.dimen.bouncer_user_switcher_icon_size + ) Icon.scaleDownIfNecessary(bitmap, iconSize, iconSize) } - } - + } if (userIcon != null) { return BitmapDrawable(userIcon) diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java deleted file mode 100644 index 58b1edcc55119..0000000000000 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java +++ /dev/null @@ -1,732 +0,0 @@ -/* - * Copyright (C) 2020 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.keyguard; - -import static com.android.keyguard.KeyguardSecurityContainer.MODE_DEFAULT; -import static com.android.keyguard.KeyguardSecurityContainer.MODE_ONE_HANDED; -import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE; - -import static com.google.common.truth.Truth.assertThat; - -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.clearInvocations; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.res.Configuration; -import android.content.res.Resources; -import android.hardware.biometrics.BiometricOverlayConstants; -import android.media.AudioManager; -import android.telephony.TelephonyManager; -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; -import android.testing.TestableResources; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.WindowInsetsController; -import android.widget.FrameLayout; - -import androidx.test.filters.SmallTest; - -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.UiEventLogger; -import com.android.internal.widget.LockPatternUtils; -import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.systemui.R; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.biometrics.SideFpsController; -import com.android.systemui.biometrics.SideFpsUiRequestSource; -import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor; -import com.android.systemui.classifier.FalsingA11yDelegate; -import com.android.systemui.classifier.FalsingCollector; -import com.android.systemui.flags.FakeFeatureFlags; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; -import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; -import com.android.systemui.log.SessionTracker; -import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.statusbar.policy.UserSwitcherController; -import com.android.systemui.util.settings.GlobalSettings; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.ArgumentMatcher; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; - -import java.util.Optional; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper() -public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { - private static final int TARGET_USER_ID = 100; - @Rule - public MockitoRule mRule = MockitoJUnit.rule(); - @Mock - private KeyguardSecurityContainer mView; - @Mock - private AdminSecondaryLockScreenController.Factory mAdminSecondaryLockScreenControllerFactory; - @Mock - private AdminSecondaryLockScreenController mAdminSecondaryLockScreenController; - @Mock - private LockPatternUtils mLockPatternUtils; - @Mock - private KeyguardUpdateMonitor mKeyguardUpdateMonitor; - @Mock - private KeyguardSecurityModel mKeyguardSecurityModel; - @Mock - private MetricsLogger mMetricsLogger; - @Mock - private UiEventLogger mUiEventLogger; - @Mock - private KeyguardStateController mKeyguardStateController; - @Mock - private KeyguardInputViewController mInputViewController; - @Mock - private WindowInsetsController mWindowInsetsController; - @Mock - private KeyguardSecurityViewFlipper mSecurityViewFlipper; - @Mock - private KeyguardSecurityViewFlipperController mKeyguardSecurityViewFlipperController; - @Mock - private KeyguardMessageAreaController.Factory mKeyguardMessageAreaControllerFactory; - @Mock - private KeyguardMessageAreaController mKeyguardMessageAreaController; - @Mock - private BouncerKeyguardMessageArea mKeyguardMessageArea; - @Mock - private ConfigurationController mConfigurationController; - @Mock - private EmergencyButtonController mEmergencyButtonController; - @Mock - private FalsingCollector mFalsingCollector; - @Mock - private FalsingManager mFalsingManager; - @Mock - private GlobalSettings mGlobalSettings; - @Mock - private FeatureFlags mFeatureFlags; - @Mock - private UserSwitcherController mUserSwitcherController; - @Mock - private SessionTracker mSessionTracker; - @Mock - private KeyguardViewController mKeyguardViewController; - @Mock - private SideFpsController mSideFpsController; - @Mock - private KeyguardPasswordViewController mKeyguardPasswordViewControllerMock; - @Mock - private FalsingA11yDelegate mFalsingA11yDelegate; - @Mock - private TelephonyManager mTelephonyManager; - @Mock - private ViewMediatorCallback mViewMediatorCallback; - @Mock - private AudioManager mAudioManager; - - @Captor - private ArgumentCaptor mKeyguardUpdateMonitorCallback; - @Captor - private ArgumentCaptor mSwipeListenerArgumentCaptor; - - @Captor - private ArgumentCaptor - mOnViewInflatedCallbackArgumentCaptor; - - private KeyguardSecurityContainerController mKeyguardSecurityContainerController; - private KeyguardPasswordViewController mKeyguardPasswordViewController; - private KeyguardPasswordView mKeyguardPasswordView; - private TestableResources mTestableResources; - - @Before - public void setup() { - mTestableResources = mContext.getOrCreateTestableResources(); - mTestableResources.getResources().getConfiguration().orientation = - Configuration.ORIENTATION_UNDEFINED; - - when(mView.getContext()).thenReturn(mContext); - when(mView.getResources()).thenReturn(mTestableResources.getResources()); - FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(/* width= */ 0, /* height= */ - 0); - lp.gravity = 0; - when(mView.getLayoutParams()).thenReturn(lp); - when(mAdminSecondaryLockScreenControllerFactory.create(any(KeyguardSecurityCallback.class))) - .thenReturn(mAdminSecondaryLockScreenController); - when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController); - mKeyguardPasswordView = spy((KeyguardPasswordView) LayoutInflater.from(mContext).inflate( - R.layout.keyguard_password_view, null)); - when(mKeyguardPasswordView.getRootView()).thenReturn(mSecurityViewFlipper); - when(mKeyguardPasswordView.requireViewById(R.id.bouncer_message_area)) - .thenReturn(mKeyguardMessageArea); - when(mKeyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea.class))) - .thenReturn(mKeyguardMessageAreaController); - when(mKeyguardPasswordView.getWindowInsetsController()).thenReturn(mWindowInsetsController); - when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(SecurityMode.PIN); - when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true); - FakeFeatureFlags featureFlags = new FakeFeatureFlags(); - featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true); - - mKeyguardPasswordViewController = new KeyguardPasswordViewController( - (KeyguardPasswordView) mKeyguardPasswordView, mKeyguardUpdateMonitor, - SecurityMode.Password, mLockPatternUtils, null, - mKeyguardMessageAreaControllerFactory, null, null, mEmergencyButtonController, - null, mock(Resources.class), null, mKeyguardViewController, - featureFlags); - - mKeyguardSecurityContainerController = new KeyguardSecurityContainerController( - mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils, - mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger, - mKeyguardStateController, mKeyguardSecurityViewFlipperController, - mConfigurationController, mFalsingCollector, mFalsingManager, - mUserSwitcherController, mFeatureFlags, mGlobalSettings, - mSessionTracker, Optional.of(mSideFpsController), mFalsingA11yDelegate, - mTelephonyManager, mViewMediatorCallback, mAudioManager, - mock(KeyguardFaceAuthInteractor.class), - mock(BouncerMessageInteractor.class)); - } - - @Test - public void onInitConfiguresViewMode() { - mKeyguardSecurityContainerController.onInit(); - verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager), - eq(mUserSwitcherController), - any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class), - eq(mFalsingA11yDelegate)); - } - - @Test - public void showSecurityScreen_canInflateAllModes() { - SecurityMode[] modes = SecurityMode.values(); - for (SecurityMode mode : modes) { - when(mInputViewController.getSecurityMode()).thenReturn(mode); - mKeyguardSecurityContainerController.showSecurityScreen(mode); - if (mode == SecurityMode.Invalid) { - verify(mKeyguardSecurityViewFlipperController, never()).getSecurityView( - any(SecurityMode.class), any(KeyguardSecurityCallback.class), any( - KeyguardSecurityViewFlipperController.OnViewInflatedCallback.class) - ); - } else { - verify(mKeyguardSecurityViewFlipperController).getSecurityView( - eq(mode), any(KeyguardSecurityCallback.class), any( - KeyguardSecurityViewFlipperController.OnViewInflatedCallback.class) - ); - } - } - } - - @Test - public void onResourcesUpdate_callsThroughOnRotationChange() { - clearInvocations(mView); - - // Rotation is the same, shouldn't cause an update - mKeyguardSecurityContainerController.updateResources(); - verify(mView, never()).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager), - eq(mUserSwitcherController), - any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class), - eq(mFalsingA11yDelegate)); - - // Update rotation. Should trigger update - mTestableResources.getResources().getConfiguration().orientation = - Configuration.ORIENTATION_LANDSCAPE; - - mKeyguardSecurityContainerController.updateResources(); - verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager), - eq(mUserSwitcherController), - any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class), - eq(mFalsingA11yDelegate)); - } - - private void touchDown() { - mKeyguardSecurityContainerController.mGlobalTouchListener.onTouchEvent( - MotionEvent.obtain( - /* downTime= */0, - /* eventTime= */0, - MotionEvent.ACTION_DOWN, - /* x= */0, - /* y= */0, - /* metaState= */0)); - } - - @Test - public void onInterceptTap_inhibitsFalsingInSidedSecurityMode() { - - when(mView.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(false); - touchDown(); - verify(mFalsingCollector, never()).avoidGesture(); - - when(mView.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(true); - touchDown(); - verify(mFalsingCollector).avoidGesture(); - } - - @Test - public void showSecurityScreen_oneHandedMode_flagDisabled_noOneHandedMode() { - mTestableResources.addOverride(R.bool.can_use_one_handed_bouncer, false); - setupGetSecurityView(SecurityMode.Pattern); - - mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern); - verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager), - eq(mUserSwitcherController), - any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class), - eq(mFalsingA11yDelegate)); - } - - @Test - public void showSecurityScreen_oneHandedMode_flagEnabled_oneHandedMode() { - mTestableResources.addOverride(R.bool.can_use_one_handed_bouncer, true); - setupGetSecurityView(SecurityMode.Pattern); - verify(mView).initMode(eq(MODE_ONE_HANDED), eq(mGlobalSettings), eq(mFalsingManager), - eq(mUserSwitcherController), - any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class), - eq(mFalsingA11yDelegate)); - } - - @Test - public void showSecurityScreen_twoHandedMode_flagEnabled_noOneHandedMode() { - mTestableResources.addOverride(R.bool.can_use_one_handed_bouncer, true); - setupGetSecurityView(SecurityMode.Password); - - verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager), - eq(mUserSwitcherController), - any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class), - eq(mFalsingA11yDelegate)); - } - - @Test - public void addUserSwitcherCallback() { - ArgumentCaptor - captor = ArgumentCaptor.forClass( - KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class); - setupGetSecurityView(SecurityMode.Password); - - verify(mView).initMode(anyInt(), any(GlobalSettings.class), any(FalsingManager.class), - any(UserSwitcherController.class), - captor.capture(), - eq(mFalsingA11yDelegate)); - captor.getValue().showUnlockToContinueMessage(); - getViewControllerImmediately(); - verify(mKeyguardPasswordViewControllerMock).showMessage( - /* message= */ getContext().getString(R.string.keyguard_unlock_to_continue), - /* colorState= */ null, - /* animated= */ true); - } - - @Test - public void addUserSwitchCallback() { - mKeyguardSecurityContainerController.onViewAttached(); - verify(mUserSwitcherController) - .addUserSwitchCallback(any(UserSwitcherController.UserSwitchCallback.class)); - mKeyguardSecurityContainerController.onViewDetached(); - verify(mUserSwitcherController) - .removeUserSwitchCallback(any(UserSwitcherController.UserSwitchCallback.class)); - } - - @Test - public void onBouncerVisibilityChanged_resetsScale() { - mKeyguardSecurityContainerController.onBouncerVisibilityChanged(false); - verify(mView).resetScale(); - } - - @Test - public void showNextSecurityScreenOrFinish_setsSecurityScreenToPinAfterSimPinUnlock() { - // GIVEN the current security method is SimPin - when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false); - when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)).thenReturn(false); - mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.SimPin); - - // WHEN a request is made from the SimPin screens to show the next security method - when(mKeyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.PIN); - mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish( - /* authenticated= */true, - TARGET_USER_ID, - /* bypassSecondaryLockScreen= */true, - SecurityMode.SimPin); - - // THEN the next security method of PIN is set, and the keyguard is not marked as done - - verify(mViewMediatorCallback, never()).keyguardDonePending(anyBoolean(), anyInt()); - verify(mViewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()); - assertThat(mKeyguardSecurityContainerController.getCurrentSecurityMode()) - .isEqualTo(SecurityMode.PIN); - } - - @Test - public void showNextSecurityScreenOrFinish_DeviceNotSecure() { - // GIVEN the current security method is SimPin - when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false); - when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)).thenReturn(false); - mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.SimPin); - - // WHEN a request is made from the SimPin screens to show the next security method - when(mKeyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.None); - when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true); - mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish( - /* authenticated= */true, - TARGET_USER_ID, - /* bypassSecondaryLockScreen= */true, - SecurityMode.SimPin); - - // THEN the next security method of None will dismiss keyguard. - verify(mViewMediatorCallback).keyguardDone(anyBoolean(), anyInt()); - } - - @Test - public void showNextSecurityScreenOrFinish_ignoresCallWhenSecurityMethodHasChanged() { - //GIVEN current security mode has been set to PIN - mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.PIN); - - //WHEN a request comes from SimPin to dismiss the security screens - boolean keyguardDone = mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish( - /* authenticated= */true, - TARGET_USER_ID, - /* bypassSecondaryLockScreen= */true, - SecurityMode.SimPin); - - //THEN no action has happened, which will not dismiss the security screens - assertThat(keyguardDone).isEqualTo(false); - verify(mKeyguardUpdateMonitor, never()).getUserHasTrust(anyInt()); - } - - @Test - public void showNextSecurityScreenOrFinish_SimPin_Swipe() { - // GIVEN the current security method is SimPin - when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false); - when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)).thenReturn(false); - mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.SimPin); - - // WHEN a request is made from the SimPin screens to show the next security method - when(mKeyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.None); - // WHEN security method is SWIPE - when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false); - mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish( - /* authenticated= */true, - TARGET_USER_ID, - /* bypassSecondaryLockScreen= */true, - SecurityMode.SimPin); - - // THEN the next security method of None will dismiss keyguard. - verify(mViewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()); - } - - - @Test - public void onSwipeUp_whenFaceDetectionIsNotRunning_initiatesFaceAuth() { - KeyguardSecurityContainer.SwipeListener registeredSwipeListener = - getRegisteredSwipeListener(); - when(mKeyguardUpdateMonitor.isFaceDetectionRunning()).thenReturn(false); - setupGetSecurityView(SecurityMode.Password); - - registeredSwipeListener.onSwipeUp(); - - verify(mKeyguardUpdateMonitor).requestFaceAuth( - FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER); - } - - @Test - public void onSwipeUp_whenFaceDetectionIsRunning_doesNotInitiateFaceAuth() { - KeyguardSecurityContainer.SwipeListener registeredSwipeListener = - getRegisteredSwipeListener(); - when(mKeyguardUpdateMonitor.isFaceDetectionRunning()).thenReturn(true); - - registeredSwipeListener.onSwipeUp(); - - verify(mKeyguardUpdateMonitor, never()) - .requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER); - } - - @Test - public void onSwipeUp_whenFaceDetectionIsTriggered_hidesBouncerMessage() { - KeyguardSecurityContainer.SwipeListener registeredSwipeListener = - getRegisteredSwipeListener(); - when(mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)) - .thenReturn(true); - setupGetSecurityView(SecurityMode.Password); - - clearInvocations(mKeyguardSecurityViewFlipperController); - registeredSwipeListener.onSwipeUp(); - getViewControllerImmediately(); - - verify(mKeyguardPasswordViewControllerMock).showMessage(/* message= */ - null, /* colorState= */ null, /* animated= */ true); - } - - @Test - public void onSwipeUp_whenFaceDetectionIsNotTriggered_retainsBouncerMessage() { - KeyguardSecurityContainer.SwipeListener registeredSwipeListener = - getRegisteredSwipeListener(); - when(mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)) - .thenReturn(false); - setupGetSecurityView(SecurityMode.Password); - - registeredSwipeListener.onSwipeUp(); - - verify(mKeyguardPasswordViewControllerMock, never()).showMessage(/* message= */ - null, /* colorState= */ null, /* animated= */ true); - } - - @Test - public void onDensityOrFontScaleChanged() { - ArgumentCaptor - configurationListenerArgumentCaptor = ArgumentCaptor.forClass( - ConfigurationController.ConfigurationListener.class); - mKeyguardSecurityContainerController.onViewAttached(); - verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture()); - clearInvocations(mKeyguardSecurityViewFlipperController); - - configurationListenerArgumentCaptor.getValue().onDensityOrFontScaleChanged(); - - verify(mKeyguardSecurityViewFlipperController).clearViews(); - verify(mKeyguardSecurityViewFlipperController).asynchronouslyInflateView( - eq(SecurityMode.PIN), - any(KeyguardSecurityCallback.class), - mOnViewInflatedCallbackArgumentCaptor.capture()); - - mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(mInputViewController); - - verify(mView).onDensityOrFontScaleChanged(); - } - - @Test - public void onThemeChanged() { - ArgumentCaptor - configurationListenerArgumentCaptor = ArgumentCaptor.forClass( - ConfigurationController.ConfigurationListener.class); - mKeyguardSecurityContainerController.onViewAttached(); - verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture()); - clearInvocations(mKeyguardSecurityViewFlipperController); - - configurationListenerArgumentCaptor.getValue().onThemeChanged(); - - verify(mKeyguardSecurityViewFlipperController).clearViews(); - verify(mKeyguardSecurityViewFlipperController).asynchronouslyInflateView( - eq(SecurityMode.PIN), - any(KeyguardSecurityCallback.class), - mOnViewInflatedCallbackArgumentCaptor.capture()); - - mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(mInputViewController); - - verify(mView).reset(); - verify(mKeyguardSecurityViewFlipperController).reset(); - verify(mView).reloadColors(); - } - - @Test - public void onUiModeChanged() { - ArgumentCaptor - configurationListenerArgumentCaptor = ArgumentCaptor.forClass( - ConfigurationController.ConfigurationListener.class); - mKeyguardSecurityContainerController.onViewAttached(); - verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture()); - clearInvocations(mKeyguardSecurityViewFlipperController); - - configurationListenerArgumentCaptor.getValue().onUiModeChanged(); - - verify(mKeyguardSecurityViewFlipperController).clearViews(); - verify(mKeyguardSecurityViewFlipperController).asynchronouslyInflateView( - eq(SecurityMode.PIN), - any(KeyguardSecurityCallback.class), - mOnViewInflatedCallbackArgumentCaptor.capture()); - - mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(mInputViewController); - - verify(mView).reloadColors(); - } - - @Test - public void testHasDismissActions() { - assertFalse("Action not set yet", mKeyguardSecurityContainerController.hasDismissActions()); - mKeyguardSecurityContainerController.setOnDismissAction(mock( - ActivityStarter.OnDismissAction.class), - null /* cancelAction */); - assertTrue("Action should exist", mKeyguardSecurityContainerController.hasDismissActions()); - } - - @Test - public void testWillRunDismissFromKeyguardIsTrue() { - ActivityStarter.OnDismissAction action = mock(ActivityStarter.OnDismissAction.class); - when(action.willRunAnimationOnKeyguard()).thenReturn(true); - mKeyguardSecurityContainerController.setOnDismissAction(action, null /* cancelAction */); - - mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */); - - assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isTrue(); - } - - @Test - public void testWillRunDismissFromKeyguardIsFalse() { - ActivityStarter.OnDismissAction action = mock(ActivityStarter.OnDismissAction.class); - when(action.willRunAnimationOnKeyguard()).thenReturn(false); - mKeyguardSecurityContainerController.setOnDismissAction(action, null /* cancelAction */); - - mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */); - - assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isFalse(); - } - - @Test - public void testWillRunDismissFromKeyguardIsFalseWhenNoDismissActionSet() { - mKeyguardSecurityContainerController.setOnDismissAction(null /* action */, - null /* cancelAction */); - - mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */); - - assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isFalse(); - } - - @Test - public void testOnStartingToHide() { - mKeyguardSecurityContainerController.onStartingToHide(); - verify(mKeyguardSecurityViewFlipperController).getSecurityView(any(SecurityMode.class), - any(KeyguardSecurityCallback.class), - mOnViewInflatedCallbackArgumentCaptor.capture()); - - mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(mInputViewController); - verify(mInputViewController).onStartingToHide(); - } - - @Test - public void testGravityReappliedOnConfigurationChange() { - // Set initial gravity - mTestableResources.addOverride(R.integer.keyguard_host_view_gravity, - Gravity.CENTER); - mTestableResources.addOverride( - R.bool.can_use_one_handed_bouncer, false); - - // Kick off the initial pass... - mKeyguardSecurityContainerController.onInit(); - verify(mView).setLayoutParams(any()); - clearInvocations(mView); - - // Now simulate a config change - mTestableResources.addOverride(R.integer.keyguard_host_view_gravity, - Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM); - - mKeyguardSecurityContainerController.updateResources(); - verify(mView).setLayoutParams(any()); - } - - @Test - public void testGravityUsesOneHandGravityWhenApplicable() { - mTestableResources.addOverride( - R.integer.keyguard_host_view_gravity, - Gravity.CENTER); - mTestableResources.addOverride( - R.integer.keyguard_host_view_one_handed_gravity, - Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM); - - // Start disabled. - mTestableResources.addOverride( - R.bool.can_use_one_handed_bouncer, false); - - mKeyguardSecurityContainerController.onInit(); - verify(mView).setLayoutParams(argThat( - (ArgumentMatcher) argument -> - argument.gravity == Gravity.CENTER)); - clearInvocations(mView); - - // And enable - mTestableResources.addOverride( - R.bool.can_use_one_handed_bouncer, true); - - mKeyguardSecurityContainerController.updateResources(); - verify(mView).setLayoutParams(argThat( - (ArgumentMatcher) argument -> - argument.gravity == (Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM))); - } - - @Test - public void testUpdateKeyguardPositionDelegatesToSecurityContainer() { - mKeyguardSecurityContainerController.updateKeyguardPosition(1.0f); - verify(mView).updatePositionByTouchX(1.0f); - } - - @Test - public void testReinflateViewFlipper() { - KeyguardSecurityViewFlipperController.OnViewInflatedCallback onViewInflatedCallback = - controller -> { - }; - mKeyguardSecurityContainerController.reinflateViewFlipper(onViewInflatedCallback); - verify(mKeyguardSecurityViewFlipperController).clearViews(); - verify(mKeyguardSecurityViewFlipperController).asynchronouslyInflateView( - any(SecurityMode.class), - any(KeyguardSecurityCallback.class), eq(onViewInflatedCallback)); - } - - @Test - public void testSideFpsControllerShow() { - mKeyguardSecurityContainerController.updateSideFpsVisibility(/* isVisible= */ true); - verify(mSideFpsController).show( - SideFpsUiRequestSource.PRIMARY_BOUNCER, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD); - } - - @Test - public void testSideFpsControllerHide() { - mKeyguardSecurityContainerController.updateSideFpsVisibility(/* isVisible= */ false); - verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER); - } - - @Test - public void setExpansion_setsAlpha() { - mKeyguardSecurityContainerController.setExpansion(EXPANSION_VISIBLE); - - verify(mView).setAlpha(1f); - verify(mView).setTranslationY(0f); - } - - private KeyguardSecurityContainer.SwipeListener getRegisteredSwipeListener() { - mKeyguardSecurityContainerController.onViewAttached(); - verify(mView).setSwipeListener(mSwipeListenerArgumentCaptor.capture()); - return mSwipeListenerArgumentCaptor.getValue(); - } - - private void setupGetSecurityView(SecurityMode securityMode) { - mKeyguardSecurityContainerController.showSecurityScreen(securityMode); - getViewControllerImmediately(); - } - - private void getViewControllerImmediately() { - verify(mKeyguardSecurityViewFlipperController, atLeastOnce()).getSecurityView( - any(SecurityMode.class), any(), - mOnViewInflatedCallbackArgumentCaptor.capture()); - mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated( - (KeyguardInputViewController) mKeyguardPasswordViewControllerMock); - - } -} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt new file mode 100644 index 0000000000000..d44717420bdf8 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -0,0 +1,810 @@ +/* + * 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. + */ +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.keyguard + +import android.content.res.Configuration +import android.hardware.biometrics.BiometricOverlayConstants +import android.media.AudioManager +import android.telephony.TelephonyManager +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import android.testing.TestableResources +import android.view.Gravity +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.WindowInsetsController +import android.widget.FrameLayout +import androidx.test.filters.SmallTest +import com.android.internal.logging.MetricsLogger +import com.android.internal.logging.UiEventLogger +import com.android.internal.widget.LockPatternUtils +import com.android.keyguard.KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback +import com.android.keyguard.KeyguardSecurityModel.SecurityMode +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.SideFpsController +import com.android.systemui.biometrics.SideFpsUiRequestSource +import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants +import com.android.systemui.classifier.FalsingA11yDelegate +import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.log.SessionTracker +import com.android.systemui.plugins.ActivityStarter.OnDismissAction +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.scene.SceneTestUtils +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.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.statusbar.policy.UserSwitcherController +import com.android.systemui.user.domain.interactor.UserInteractor +import com.android.systemui.util.kotlin.JavaAdapter +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argThat +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.settings.GlobalSettings +import com.google.common.truth.Truth +import java.util.Optional +import junit.framework.Assert +import kotlinx.coroutines.ExperimentalCoroutinesApi +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.mockito.ArgumentCaptor +import org.mockito.ArgumentMatcher +import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.eq +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.atLeastOnce +import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.never +import org.mockito.Mockito.spy +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class KeyguardSecurityContainerControllerTest : SysuiTestCase() { + + @Mock private lateinit var view: KeyguardSecurityContainer + @Mock + private lateinit var adminSecondaryLockScreenControllerFactory: + AdminSecondaryLockScreenController.Factory + @Mock + private lateinit var adminSecondaryLockScreenController: AdminSecondaryLockScreenController + @Mock private lateinit var lockPatternUtils: LockPatternUtils + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel + @Mock private lateinit var metricsLogger: MetricsLogger + @Mock private lateinit var uiEventLogger: UiEventLogger + @Mock private lateinit var keyguardStateController: KeyguardStateController + @Mock private lateinit var inputViewController: KeyguardInputViewController + @Mock private lateinit var windowInsetsController: WindowInsetsController + @Mock private lateinit var securityViewFlipper: KeyguardSecurityViewFlipper + @Mock private lateinit var viewFlipperController: KeyguardSecurityViewFlipperController + @Mock private lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory + @Mock private lateinit var keyguardMessageAreaController: KeyguardMessageAreaController<*> + @Mock private lateinit var keyguardMessageArea: BouncerKeyguardMessageArea + @Mock private lateinit var configurationController: ConfigurationController + @Mock private lateinit var emergencyButtonController: EmergencyButtonController + @Mock private lateinit var falsingCollector: FalsingCollector + @Mock private lateinit var falsingManager: FalsingManager + @Mock private lateinit var globalSettings: GlobalSettings + @Mock private lateinit var userSwitcherController: UserSwitcherController + @Mock private lateinit var sessionTracker: SessionTracker + @Mock private lateinit var keyguardViewController: KeyguardViewController + @Mock private lateinit var sideFpsController: SideFpsController + @Mock private lateinit var keyguardPasswordViewControllerMock: KeyguardPasswordViewController + @Mock private lateinit var falsingA11yDelegate: FalsingA11yDelegate + @Mock private lateinit var telephonyManager: TelephonyManager + @Mock private lateinit var viewMediatorCallback: ViewMediatorCallback + @Mock private lateinit var audioManager: AudioManager + @Mock private lateinit var userInteractor: UserInteractor + + @Captor + private lateinit var swipeListenerArgumentCaptor: + ArgumentCaptor + @Captor + private lateinit var onViewInflatedCallbackArgumentCaptor: + ArgumentCaptor + + private lateinit var featureFlags: FakeFeatureFlags + private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController + private lateinit var keyguardPasswordView: KeyguardPasswordView + private lateinit var testableResources: TestableResources + private lateinit var sceneTestUtils: SceneTestUtils + private lateinit var sceneInteractor: SceneInteractor + + private lateinit var underTest: KeyguardSecurityContainerController + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + testableResources = mContext.getOrCreateTestableResources() + testableResources.resources.configuration.orientation = Configuration.ORIENTATION_UNDEFINED + whenever(view.context).thenReturn(mContext) + whenever(view.resources).thenReturn(testableResources.resources) + + val lp = FrameLayout.LayoutParams(/* width= */ 0, /* height= */ 0) + lp.gravity = 0 + whenever(view.layoutParams).thenReturn(lp) + + whenever(adminSecondaryLockScreenControllerFactory.create(any())) + .thenReturn(adminSecondaryLockScreenController) + whenever(securityViewFlipper.windowInsetsController).thenReturn(windowInsetsController) + keyguardPasswordView = + spy( + LayoutInflater.from(mContext).inflate(R.layout.keyguard_password_view, null) + as KeyguardPasswordView + ) + whenever(keyguardPasswordView.rootView).thenReturn(securityViewFlipper) + whenever(keyguardPasswordView.requireViewById(R.id.bouncer_message_area)) + .thenReturn(keyguardMessageArea) + whenever(messageAreaControllerFactory.create(any())) + .thenReturn(keyguardMessageAreaController) + whenever(keyguardPasswordView.windowInsetsController).thenReturn(windowInsetsController) + whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(SecurityMode.PIN) + whenever(keyguardStateController.canDismissLockScreen()).thenReturn(true) + + featureFlags = FakeFeatureFlags() + featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true) + featureFlags.set(Flags.SCENE_CONTAINER, false) + featureFlags.set(Flags.BOUNCER_USER_SWITCHER, false) + + keyguardPasswordViewController = + KeyguardPasswordViewController( + keyguardPasswordView, + keyguardUpdateMonitor, + SecurityMode.Password, + lockPatternUtils, + null, + messageAreaControllerFactory, + null, + null, + emergencyButtonController, + null, + mock(), + null, + keyguardViewController, + featureFlags + ) + + whenever(userInteractor.getSelectedUserId()).thenReturn(TARGET_USER_ID) + sceneTestUtils = SceneTestUtils(this) + sceneInteractor = sceneTestUtils.sceneInteractor() + + underTest = + KeyguardSecurityContainerController( + view, + adminSecondaryLockScreenControllerFactory, + lockPatternUtils, + keyguardUpdateMonitor, + keyguardSecurityModel, + metricsLogger, + uiEventLogger, + keyguardStateController, + viewFlipperController, + configurationController, + falsingCollector, + falsingManager, + userSwitcherController, + featureFlags, + globalSettings, + sessionTracker, + Optional.of(sideFpsController), + falsingA11yDelegate, + telephonyManager, + viewMediatorCallback, + audioManager, + mock(), + mock(), + { JavaAdapter(sceneTestUtils.testScope.backgroundScope) }, + userInteractor, + ) { + sceneInteractor + } + } + + @Test + fun onInitConfiguresViewMode() { + underTest.onInit() + verify(view) + .initMode( + eq(KeyguardSecurityContainer.MODE_DEFAULT), + eq(globalSettings), + eq(falsingManager), + eq(userSwitcherController), + any(), + eq(falsingA11yDelegate) + ) + } + + @Test + fun showSecurityScreen_canInflateAllModes() { + val modes = SecurityMode.values() + for (mode in modes) { + whenever(inputViewController.securityMode).thenReturn(mode) + underTest.showSecurityScreen(mode) + if (mode == SecurityMode.Invalid) { + verify(viewFlipperController, never()).getSecurityView(any(), any(), any()) + } else { + verify(viewFlipperController).getSecurityView(eq(mode), any(), any()) + } + } + } + + @Test + fun onResourcesUpdate_callsThroughOnRotationChange() { + clearInvocations(view) + + // Rotation is the same, shouldn't cause an update + underTest.updateResources() + verify(view, never()) + .initMode( + eq(KeyguardSecurityContainer.MODE_DEFAULT), + eq(globalSettings), + eq(falsingManager), + eq(userSwitcherController), + any(), + eq(falsingA11yDelegate) + ) + + // Update rotation. Should trigger update + testableResources.resources.configuration.orientation = Configuration.ORIENTATION_LANDSCAPE + underTest.updateResources() + verify(view) + .initMode( + eq(KeyguardSecurityContainer.MODE_DEFAULT), + eq(globalSettings), + eq(falsingManager), + eq(userSwitcherController), + any(), + eq(falsingA11yDelegate) + ) + } + + private fun touchDown() { + underTest.mGlobalTouchListener.onTouchEvent( + MotionEvent.obtain( + /* downTime= */ 0, + /* eventTime= */ 0, + MotionEvent.ACTION_DOWN, + /* x= */ 0f, + /* y= */ 0f, + /* metaState= */ 0 + ) + ) + } + + @Test + fun onInterceptTap_inhibitsFalsingInSidedSecurityMode() { + whenever(view.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(false) + touchDown() + verify(falsingCollector, never()).avoidGesture() + whenever(view.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(true) + touchDown() + verify(falsingCollector).avoidGesture() + } + + @Test + fun showSecurityScreen_oneHandedMode_flagDisabled_noOneHandedMode() { + testableResources.addOverride(R.bool.can_use_one_handed_bouncer, false) + setupGetSecurityView(SecurityMode.Pattern) + underTest.showSecurityScreen(SecurityMode.Pattern) + verify(view) + .initMode( + eq(KeyguardSecurityContainer.MODE_DEFAULT), + eq(globalSettings), + eq(falsingManager), + eq(userSwitcherController), + any(), + eq(falsingA11yDelegate) + ) + } + + @Test + fun showSecurityScreen_oneHandedMode_flagEnabled_oneHandedMode() { + testableResources.addOverride(R.bool.can_use_one_handed_bouncer, true) + setupGetSecurityView(SecurityMode.Pattern) + verify(view) + .initMode( + eq(KeyguardSecurityContainer.MODE_ONE_HANDED), + eq(globalSettings), + eq(falsingManager), + eq(userSwitcherController), + any(), + eq(falsingA11yDelegate) + ) + } + + @Test + fun showSecurityScreen_twoHandedMode_flagEnabled_noOneHandedMode() { + testableResources.addOverride(R.bool.can_use_one_handed_bouncer, true) + setupGetSecurityView(SecurityMode.Password) + verify(view) + .initMode( + eq(KeyguardSecurityContainer.MODE_DEFAULT), + eq(globalSettings), + eq(falsingManager), + eq(userSwitcherController), + any(), + eq(falsingA11yDelegate) + ) + } + + @Test + fun addUserSwitcherCallback() { + val captor = ArgumentCaptor.forClass(UserSwitcherCallback::class.java) + setupGetSecurityView(SecurityMode.Password) + verify(view) + .initMode(anyInt(), any(), any(), any(), captor.capture(), eq(falsingA11yDelegate)) + captor.value.showUnlockToContinueMessage() + viewControllerImmediately + verify(keyguardPasswordViewControllerMock) + .showMessage( + /* message= */ context.getString(R.string.keyguard_unlock_to_continue), + /* colorState= */ null, + /* animated= */ true + ) + } + + @Test + fun addUserSwitchCallback() { + underTest.onViewAttached() + verify(userSwitcherController).addUserSwitchCallback(any()) + underTest.onViewDetached() + verify(userSwitcherController).removeUserSwitchCallback(any()) + } + + @Test + fun onBouncerVisibilityChanged_resetsScale() { + underTest.onBouncerVisibilityChanged(false) + verify(view).resetScale() + } + + @Test + fun showNextSecurityScreenOrFinish_setsSecurityScreenToPinAfterSimPinUnlock() { + // GIVEN the current security method is SimPin + whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false) + whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)) + .thenReturn(false) + underTest.showSecurityScreen(SecurityMode.SimPin) + + // WHEN a request is made from the SimPin screens to show the next security method + whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.PIN) + underTest.showNextSecurityScreenOrFinish( + /* authenticated= */ true, + TARGET_USER_ID, + /* bypassSecondaryLockScreen= */ true, + SecurityMode.SimPin + ) + + // THEN the next security method of PIN is set, and the keyguard is not marked as done + verify(viewMediatorCallback, never()).keyguardDonePending(anyBoolean(), anyInt()) + verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) + Truth.assertThat(underTest.currentSecurityMode).isEqualTo(SecurityMode.PIN) + } + + @Test + fun showNextSecurityScreenOrFinish_DeviceNotSecure() { + // GIVEN the current security method is SimPin + whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false) + whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)) + .thenReturn(false) + underTest.showSecurityScreen(SecurityMode.SimPin) + + // WHEN a request is made from the SimPin screens to show the next security method + whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID)) + .thenReturn(SecurityMode.None) + whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true) + underTest.showNextSecurityScreenOrFinish( + /* authenticated= */ true, + TARGET_USER_ID, + /* bypassSecondaryLockScreen= */ true, + SecurityMode.SimPin + ) + + // THEN the next security method of None will dismiss keyguard. + verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt()) + } + + @Test + fun showNextSecurityScreenOrFinish_ignoresCallWhenSecurityMethodHasChanged() { + // GIVEN current security mode has been set to PIN + underTest.showSecurityScreen(SecurityMode.PIN) + + // WHEN a request comes from SimPin to dismiss the security screens + val keyguardDone = + underTest.showNextSecurityScreenOrFinish( + /* authenticated= */ true, + TARGET_USER_ID, + /* bypassSecondaryLockScreen= */ true, + SecurityMode.SimPin + ) + + // THEN no action has happened, which will not dismiss the security screens + Truth.assertThat(keyguardDone).isEqualTo(false) + verify(keyguardUpdateMonitor, never()).getUserHasTrust(anyInt()) + } + + @Test + fun showNextSecurityScreenOrFinish_SimPin_Swipe() { + // GIVEN the current security method is SimPin + whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false) + whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)) + .thenReturn(false) + underTest.showSecurityScreen(SecurityMode.SimPin) + + // WHEN a request is made from the SimPin screens to show the next security method + whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID)) + .thenReturn(SecurityMode.None) + // WHEN security method is SWIPE + whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false) + underTest.showNextSecurityScreenOrFinish( + /* authenticated= */ true, + TARGET_USER_ID, + /* bypassSecondaryLockScreen= */ true, + SecurityMode.SimPin + ) + + // THEN the next security method of None will dismiss keyguard. + verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) + } + + @Test + fun onSwipeUp_whenFaceDetectionIsNotRunning_initiatesFaceAuth() { + val registeredSwipeListener = registeredSwipeListener + whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(false) + setupGetSecurityView(SecurityMode.Password) + registeredSwipeListener.onSwipeUp() + verify(keyguardUpdateMonitor).requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER) + } + + @Test + fun onSwipeUp_whenFaceDetectionIsRunning_doesNotInitiateFaceAuth() { + val registeredSwipeListener = registeredSwipeListener + whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(true) + registeredSwipeListener.onSwipeUp() + verify(keyguardUpdateMonitor, never()) + .requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER) + } + + @Test + fun onSwipeUp_whenFaceDetectionIsTriggered_hidesBouncerMessage() { + val registeredSwipeListener = registeredSwipeListener + whenever( + keyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER) + ) + .thenReturn(true) + setupGetSecurityView(SecurityMode.Password) + clearInvocations(viewFlipperController) + registeredSwipeListener.onSwipeUp() + viewControllerImmediately + verify(keyguardPasswordViewControllerMock) + .showMessage(/* message= */ null, /* colorState= */ null, /* animated= */ true) + } + + @Test + fun onSwipeUp_whenFaceDetectionIsNotTriggered_retainsBouncerMessage() { + val registeredSwipeListener = registeredSwipeListener + whenever( + keyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER) + ) + .thenReturn(false) + setupGetSecurityView(SecurityMode.Password) + registeredSwipeListener.onSwipeUp() + verify(keyguardPasswordViewControllerMock, never()) + .showMessage(/* message= */ null, /* colorState= */ null, /* animated= */ true) + } + + @Test + fun onDensityOrFontScaleChanged() { + val configurationListenerArgumentCaptor = + ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java) + underTest.onViewAttached() + verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture()) + clearInvocations(viewFlipperController) + configurationListenerArgumentCaptor.value.onDensityOrFontScaleChanged() + verify(viewFlipperController).clearViews() + verify(viewFlipperController) + .asynchronouslyInflateView( + eq(SecurityMode.PIN), + any(), + onViewInflatedCallbackArgumentCaptor.capture() + ) + onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController) + verify(view).onDensityOrFontScaleChanged() + } + + @Test + fun onThemeChanged() { + val configurationListenerArgumentCaptor = + ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java) + underTest.onViewAttached() + verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture()) + clearInvocations(viewFlipperController) + configurationListenerArgumentCaptor.value.onThemeChanged() + verify(viewFlipperController).clearViews() + verify(viewFlipperController) + .asynchronouslyInflateView( + eq(SecurityMode.PIN), + any(), + onViewInflatedCallbackArgumentCaptor.capture() + ) + onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController) + verify(view).reset() + verify(viewFlipperController).reset() + verify(view).reloadColors() + } + + @Test + fun onUiModeChanged() { + val configurationListenerArgumentCaptor = + ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java) + underTest.onViewAttached() + verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture()) + clearInvocations(viewFlipperController) + configurationListenerArgumentCaptor.value.onUiModeChanged() + verify(viewFlipperController).clearViews() + verify(viewFlipperController) + .asynchronouslyInflateView( + eq(SecurityMode.PIN), + any(), + onViewInflatedCallbackArgumentCaptor.capture() + ) + onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController) + verify(view).reloadColors() + } + + @Test + fun hasDismissActions() { + Assert.assertFalse("Action not set yet", underTest.hasDismissActions()) + underTest.setOnDismissAction(mock(), null /* cancelAction */) + Assert.assertTrue("Action should exist", underTest.hasDismissActions()) + } + + @Test + fun willRunDismissFromKeyguardIsTrue() { + val action: OnDismissAction = mock() + whenever(action.willRunAnimationOnKeyguard()).thenReturn(true) + underTest.setOnDismissAction(action, null /* cancelAction */) + underTest.finish(false /* strongAuth */, 0 /* currentUser */) + Truth.assertThat(underTest.willRunDismissFromKeyguard()).isTrue() + } + + @Test + fun willRunDismissFromKeyguardIsFalse() { + val action: OnDismissAction = mock() + whenever(action.willRunAnimationOnKeyguard()).thenReturn(false) + underTest.setOnDismissAction(action, null /* cancelAction */) + underTest.finish(false /* strongAuth */, 0 /* currentUser */) + Truth.assertThat(underTest.willRunDismissFromKeyguard()).isFalse() + } + + @Test + fun willRunDismissFromKeyguardIsFalseWhenNoDismissActionSet() { + underTest.setOnDismissAction(null /* action */, null /* cancelAction */) + underTest.finish(false /* strongAuth */, 0 /* currentUser */) + Truth.assertThat(underTest.willRunDismissFromKeyguard()).isFalse() + } + + @Test + fun onStartingToHide() { + underTest.onStartingToHide() + verify(viewFlipperController) + .getSecurityView(any(), any(), onViewInflatedCallbackArgumentCaptor.capture()) + onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController) + verify(inputViewController).onStartingToHide() + } + + @Test + fun gravityReappliedOnConfigurationChange() { + // Set initial gravity + testableResources.addOverride(R.integer.keyguard_host_view_gravity, Gravity.CENTER) + testableResources.addOverride(R.bool.can_use_one_handed_bouncer, false) + + // Kick off the initial pass... + underTest.onInit() + verify(view).layoutParams = any() + clearInvocations(view) + + // Now simulate a config change + testableResources.addOverride( + R.integer.keyguard_host_view_gravity, + Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM + ) + underTest.updateResources() + verify(view).layoutParams = any() + } + + @Test + fun gravityUsesOneHandGravityWhenApplicable() { + testableResources.addOverride(R.integer.keyguard_host_view_gravity, Gravity.CENTER) + testableResources.addOverride( + R.integer.keyguard_host_view_one_handed_gravity, + Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM + ) + + // Start disabled. + testableResources.addOverride(R.bool.can_use_one_handed_bouncer, false) + underTest.onInit() + verify(view).layoutParams = + argThat( + ArgumentMatcher { argument: FrameLayout.LayoutParams -> + argument.gravity == Gravity.CENTER + } + as ArgumentMatcher + ) + clearInvocations(view) + + // And enable + testableResources.addOverride(R.bool.can_use_one_handed_bouncer, true) + underTest.updateResources() + verify(view).layoutParams = + argThat( + ArgumentMatcher { argument: FrameLayout.LayoutParams -> + argument.gravity == Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM + } + as ArgumentMatcher + ) + } + + @Test + fun updateKeyguardPositionDelegatesToSecurityContainer() { + underTest.updateKeyguardPosition(1.0f) + verify(view).updatePositionByTouchX(1.0f) + } + + @Test + fun reinflateViewFlipper() { + val onViewInflatedCallback = KeyguardSecurityViewFlipperController.OnViewInflatedCallback {} + underTest.reinflateViewFlipper(onViewInflatedCallback) + verify(viewFlipperController).clearViews() + verify(viewFlipperController) + .asynchronouslyInflateView(any(), any(), eq(onViewInflatedCallback)) + } + + @Test + fun sideFpsControllerShow() { + underTest.updateSideFpsVisibility(/* isVisible= */ true) + verify(sideFpsController) + .show( + SideFpsUiRequestSource.PRIMARY_BOUNCER, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD + ) + } + + @Test + fun sideFpsControllerHide() { + underTest.updateSideFpsVisibility(/* isVisible= */ false) + verify(sideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER) + } + + @Test + fun setExpansion_setsAlpha() { + underTest.setExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE) + verify(view).alpha = 1f + verify(view).translationY = 0f + } + + @Test + fun dismissesKeyguard_whenSceneChangesFromBouncerToGone() = + sceneTestUtils.testScope.runTest { + featureFlags.set(Flags.SCENE_CONTAINER, true) + + // Upon init, we have never dismisses the keyguard. + underTest.onInit() + runCurrent() + verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) + + // Once the view is attached, we start listening but simply going to the bouncer scene + // is + // not enough to trigger a dismissal of the keyguard. + underTest.onViewAttached() + sceneInteractor.setCurrentScene( + SceneTestUtils.CONTAINER_1, + SceneModel(SceneKey.Bouncer, null) + ) + runCurrent() + verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) + + // While listening, going from the bouncer scene to the gone scene, does dismiss the + // keyguard. + sceneInteractor.setCurrentScene( + SceneTestUtils.CONTAINER_1, + SceneModel(SceneKey.Gone, null) + ) + runCurrent() + verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt()) + + // While listening, moving back to the bouncer scene does not dismiss the keyguard + // again. + clearInvocations(viewMediatorCallback) + sceneInteractor.setCurrentScene( + SceneTestUtils.CONTAINER_1, + SceneModel(SceneKey.Bouncer, null) + ) + runCurrent() + verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) + + // Detaching the view stops listening, so moving from the bouncer scene to the gone + // scene + // does not dismiss the keyguard while we're not listening. + underTest.onViewDetached() + sceneInteractor.setCurrentScene( + SceneTestUtils.CONTAINER_1, + SceneModel(SceneKey.Gone, null) + ) + runCurrent() + verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) + + // While not listening, moving back to the bouncer does not dismiss the keyguard. + sceneInteractor.setCurrentScene( + SceneTestUtils.CONTAINER_1, + SceneModel(SceneKey.Bouncer, null) + ) + runCurrent() + verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) + + // Reattaching the view starts listening again so moving from the bouncer scene to the + // gone + // scene now does dismiss the keyguard again. + underTest.onViewAttached() + sceneInteractor.setCurrentScene( + SceneTestUtils.CONTAINER_1, + SceneModel(SceneKey.Gone, null) + ) + runCurrent() + verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt()) + } + + private val registeredSwipeListener: KeyguardSecurityContainer.SwipeListener + get() { + underTest.onViewAttached() + verify(view).setSwipeListener(swipeListenerArgumentCaptor.capture()) + return swipeListenerArgumentCaptor.value + } + + private fun setupGetSecurityView(securityMode: SecurityMode) { + underTest.showSecurityScreen(securityMode) + viewControllerImmediately + } + + private val viewControllerImmediately: Unit + get() { + verify(viewFlipperController, atLeastOnce()) + .getSecurityView(any(), any(), onViewInflatedCallbackArgumentCaptor.capture()) + @Suppress("UNCHECKED_CAST") + onViewInflatedCallbackArgumentCaptor.value.onViewInflated( + keyguardPasswordViewControllerMock as KeyguardInputViewController + ) + } + + companion object { + private const val TARGET_USER_ID = 100 + } +} 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 ea3289cc3836e..c0c690892958d 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 @@ -20,11 +20,16 @@ import android.app.admin.DevicePolicyManager import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase 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.shared.model.AuthenticationThrottlingModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils import com.google.common.truth.Truth.assertThat +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test @@ -47,24 +52,56 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun getAuthenticationMethod() = testScope.runTest { - assertThat(underTest.getAuthenticationMethod()) - .isEqualTo(AuthenticationMethodModel.Pin(1234)) + assertThat(underTest.getAuthenticationMethod()).isEqualTo(AuthenticationMethodModel.Pin) utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password("password") + AuthenticationMethodModel.Password ) + assertThat(underTest.getAuthenticationMethod()) - .isEqualTo(AuthenticationMethodModel.Password("password")) + .isEqualTo(AuthenticationMethodModel.Password) } @Test - fun isUnlocked_whenAuthMethodIsNone_isTrue() = + fun getAuthenticationMethod_noneTreatedAsSwipe_whenLockscreenEnabled() = testScope.runTest { utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + utils.authenticationRepository.setLockscreenEnabled(true) + + assertThat(underTest.getAuthenticationMethod()) + .isEqualTo(AuthenticationMethodModel.Swipe) + } + + @Test + fun getAuthenticationMethod_none_whenLockscreenDisabled() = + testScope.runTest { + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + utils.authenticationRepository.setLockscreenEnabled(false) + + assertThat(underTest.getAuthenticationMethod()) + .isEqualTo(AuthenticationMethodModel.None) + } + + @Test + fun isUnlocked_whenAuthMethodIsNoneAndLockscreenDisabled_isTrue() = + testScope.runTest { + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + utils.authenticationRepository.setLockscreenEnabled(false) + val isUnlocked by collectLastValue(underTest.isUnlocked) assertThat(isUnlocked).isTrue() } + @Test + fun isUnlocked_whenAuthMethodIsNoneAndLockscreenEnabled_isFalse() = + testScope.runTest { + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + utils.authenticationRepository.setLockscreenEnabled(true) + + val isUnlocked by collectLastValue(underTest.isUnlocked) + assertThat(isUnlocked).isFalse() + } + @Test fun toggleBypassEnabled() = testScope.runTest { @@ -84,7 +121,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { utils.authenticationRepository.setUnlocked(false) runCurrent() utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password("password") + AuthenticationMethodModel.Password ) assertThat(underTest.isAuthenticationRequired()).isTrue() @@ -106,7 +143,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { utils.authenticationRepository.setUnlocked(true) runCurrent() utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password("password") + AuthenticationMethodModel.Password ) assertThat(underTest.isAuthenticationRequired()).isFalse() @@ -125,49 +162,33 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun authenticate_withCorrectPin_returnsTrue() = testScope.runTest { - val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234) - ) - + val isThrottled by collectLastValue(underTest.isThrottled) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue() - assertThat(failedAttemptCount).isEqualTo(0) + assertThat(isThrottled).isFalse() } @Test fun authenticate_withIncorrectPin_returnsFalse() = testScope.runTest { - val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234) - ) - + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(underTest.authenticate(listOf(9, 8, 7))).isFalse() - assertThat(failedAttemptCount).isEqualTo(1) } - @Test - fun authenticate_withEmptyPin_returnsFalse() = + @Test(expected = IllegalArgumentException::class) + fun authenticate_withEmptyPin_throwsException() = testScope.runTest { - val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234) - ) - - assertThat(underTest.authenticate(listOf())).isFalse() - assertThat(failedAttemptCount).isEqualTo(1) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + underTest.authenticate(listOf()) } @Test fun authenticate_withCorrectMaxLengthPin_returnsTrue() = testScope.runTest { - val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(9999999999999999) - ) - - assertThat(underTest.authenticate(List(16) { 9 })).isTrue() - assertThat(failedAttemptCount).isEqualTo(0) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + val pin = List(16) { 9 } + utils.authenticationRepository.overrideCredential(pin) + assertThat(underTest.authenticate(pin)).isTrue() } @Test @@ -179,105 +200,47 @@ class AuthenticationInteractorTest : SysuiTestCase() { // If the policy changes, there is work to do in SysUI. assertThat(DevicePolicyManager.MAX_PASSWORD_LENGTH).isLessThan(17) - val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(99999999999999999) - ) - + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(underTest.authenticate(List(17) { 9 })).isFalse() - assertThat(failedAttemptCount).isEqualTo(1) } @Test fun authenticate_withCorrectPassword_returnsTrue() = testScope.runTest { - val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts) + val isThrottled by collectLastValue(underTest.isThrottled) utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password("password") + AuthenticationMethodModel.Password ) assertThat(underTest.authenticate("password".toList())).isTrue() - assertThat(failedAttemptCount).isEqualTo(0) + assertThat(isThrottled).isFalse() } @Test fun authenticate_withIncorrectPassword_returnsFalse() = testScope.runTest { - val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts) utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password("password") + AuthenticationMethodModel.Password ) assertThat(underTest.authenticate("alohomora".toList())).isFalse() - assertThat(failedAttemptCount).isEqualTo(1) } @Test fun authenticate_withCorrectPattern_returnsTrue() = testScope.runTest { - val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts) utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pattern( - listOf( - AuthenticationMethodModel.Pattern.PatternCoordinate( - x = 0, - y = 0, - ), - AuthenticationMethodModel.Pattern.PatternCoordinate( - x = 0, - y = 1, - ), - AuthenticationMethodModel.Pattern.PatternCoordinate( - x = 0, - y = 2, - ), - ) - ) + AuthenticationMethodModel.Pattern ) - assertThat( - underTest.authenticate( - listOf( - AuthenticationMethodModel.Pattern.PatternCoordinate( - x = 0, - y = 0, - ), - AuthenticationMethodModel.Pattern.PatternCoordinate( - x = 0, - y = 1, - ), - AuthenticationMethodModel.Pattern.PatternCoordinate( - x = 0, - y = 2, - ), - ) - ) - ) - .isTrue() - assertThat(failedAttemptCount).isEqualTo(0) + assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN)).isTrue() } @Test fun authenticate_withIncorrectPattern_returnsFalse() = testScope.runTest { - val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts) utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pattern( - listOf( - AuthenticationMethodModel.Pattern.PatternCoordinate( - x = 0, - y = 0, - ), - AuthenticationMethodModel.Pattern.PatternCoordinate( - x = 0, - y = 1, - ), - AuthenticationMethodModel.Pattern.PatternCoordinate( - x = 0, - y = 2, - ), - ) - ) + AuthenticationMethodModel.Pattern ) assertThat( @@ -299,91 +262,159 @@ class AuthenticationInteractorTest : SysuiTestCase() { ) ) .isFalse() - assertThat(failedAttemptCount).isEqualTo(1) - } - - @Test - fun tryAutoConfirm_withAutoConfirmPinAndEmptyInput_returnsNull() = - testScope.runTest { - val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234, autoConfirm = true) - ) - - assertThat(underTest.authenticate(listOf(), tryAutoConfirm = true)).isNull() - assertThat(failedAttemptCount).isEqualTo(0) } @Test fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNullAndHasNoEffect() = testScope.runTest { - val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234, autoConfirm = true) - ) - + val isThrottled by collectLastValue(underTest.isThrottled) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAutoConfirmEnabled(true) assertThat(underTest.authenticate(listOf(1, 2, 3), tryAutoConfirm = true)).isNull() - assertThat(failedAttemptCount).isEqualTo(0) + assertThat(isThrottled).isFalse() } @Test fun tryAutoConfirm_withAutoConfirmWrongPinCorrectLength_returnsFalseAndDoesNotUnlockDevice() = testScope.runTest { - val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234, autoConfirm = true) - ) - + val isUnlocked by collectLastValue(underTest.isUnlocked) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAutoConfirmEnabled(true) assertThat(underTest.authenticate(listOf(1, 2, 4, 4), tryAutoConfirm = true)).isFalse() - assertThat(failedAttemptCount).isEqualTo(1) + assertThat(isUnlocked).isFalse() } @Test fun tryAutoConfirm_withAutoConfirmLongerPin_returnsFalseAndDoesNotUnlockDevice() = testScope.runTest { - val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234, autoConfirm = true) - ) - + val isUnlocked by collectLastValue(underTest.isUnlocked) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAutoConfirmEnabled(true) assertThat(underTest.authenticate(listOf(1, 2, 3, 4, 5), tryAutoConfirm = true)) .isFalse() - assertThat(failedAttemptCount).isEqualTo(1) + assertThat(isUnlocked).isFalse() } @Test fun tryAutoConfirm_withAutoConfirmCorrectPin_returnsTrueAndUnlocksDevice() = testScope.runTest { - val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234, autoConfirm = true) - ) - - assertThat(underTest.authenticate(listOf(1, 2, 4, 4), tryAutoConfirm = true)).isFalse() - assertThat(failedAttemptCount).isEqualTo(1) + val isUnlocked by collectLastValue(underTest.isUnlocked) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAutoConfirmEnabled(true) + assertThat(underTest.authenticate(listOf(1, 2, 3, 4), tryAutoConfirm = true)).isTrue() + assertThat(isUnlocked).isTrue() } @Test fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNullAndHasNoEffects() = testScope.runTest { - val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234, autoConfirm = false) - ) - + val isUnlocked by collectLastValue(underTest.isUnlocked) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAutoConfirmEnabled(false) assertThat(underTest.authenticate(listOf(1, 2, 3, 4), tryAutoConfirm = true)).isNull() - assertThat(failedAttemptCount).isEqualTo(0) + assertThat(isUnlocked).isFalse() } @Test fun tryAutoConfirm_withoutCorrectPassword_returnsNullAndHasNoEffects() = testScope.runTest { - val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts) + val isUnlocked by collectLastValue(underTest.isUnlocked) utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password("password") + AuthenticationMethodModel.Password ) assertThat(underTest.authenticate("password".toList(), tryAutoConfirm = true)).isNull() - assertThat(failedAttemptCount).isEqualTo(0) + assertThat(isUnlocked).isFalse() + } + + @Test + fun throttling() = + testScope.runTest { + val isUnlocked by collectLastValue(underTest.isUnlocked) + val throttling by collectLastValue(underTest.throttling) + val isThrottled by collectLastValue(underTest.isThrottled) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + underTest.authenticate(listOf(1, 2, 3, 4)) + assertThat(isUnlocked).isTrue() + assertThat(isThrottled).isFalse() + assertThat(throttling).isEqualTo(AuthenticationThrottlingModel()) + + utils.authenticationRepository.setUnlocked(false) + assertThat(isUnlocked).isFalse() + assertThat(isThrottled).isFalse() + assertThat(throttling).isEqualTo(AuthenticationThrottlingModel()) + + // Make many wrong attempts, but just shy of what's needed to get throttled: + repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1) { + underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN + assertThat(isUnlocked).isFalse() + assertThat(isThrottled).isFalse() + assertThat(throttling).isEqualTo(AuthenticationThrottlingModel()) + } + + // Make one more wrong attempt, leading to throttling: + underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN + assertThat(isUnlocked).isFalse() + assertThat(isThrottled).isTrue() + assertThat(throttling) + .isEqualTo( + AuthenticationThrottlingModel( + failedAttemptCount = + FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING, + remainingMs = FakeAuthenticationRepository.THROTTLE_DURATION_MS, + ) + ) + + // Correct PIN, but throttled, so doesn't attempt it: + assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isNull() + assertThat(isUnlocked).isFalse() + assertThat(isThrottled).isTrue() + assertThat(throttling) + .isEqualTo( + AuthenticationThrottlingModel( + failedAttemptCount = + FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING, + remainingMs = FakeAuthenticationRepository.THROTTLE_DURATION_MS, + ) + ) + + // Move the clock forward to ALMOST skip the throttling, leaving one second to go: + val throttleTimeoutSec = + FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds + .toInt() + repeat(throttleTimeoutSec - 1) { time -> + advanceTimeBy(1000) + assertThat(isThrottled).isTrue() + assertThat(throttling) + .isEqualTo( + AuthenticationThrottlingModel( + failedAttemptCount = + FakeAuthenticationRepository + .MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING, + remainingMs = + ((throttleTimeoutSec - (time + 1)).seconds.inWholeMilliseconds) + .toInt(), + ) + ) + } + + // Move the clock forward one more second, to completely finish the throttling period: + advanceTimeBy(1000) + assertThat(isUnlocked).isFalse() + assertThat(isThrottled).isFalse() + assertThat(throttling) + .isEqualTo( + AuthenticationThrottlingModel( + failedAttemptCount = + FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING, + remainingMs = 0, + ) + ) + + // Correct PIN and no longer throttled so unlocks successfully: + assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue() + assertThat(isUnlocked).isTrue() + assertThat(isThrottled).isFalse() + assertThat(throttling).isEqualTo(AuthenticationThrottlingModel()) } } 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 d09353b40e664..c2219a4d82ebf 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,12 +19,16 @@ 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.repository.FakeAuthenticationRepository import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel 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 kotlin.math.ceil +import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runCurrent @@ -65,14 +69,13 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun pinAuthMethod() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene("container1")) + val currentScene by + collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) val message by collectLastValue(underTest.message) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - underTest.showOrUnlockDevice("container1") + underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN) @@ -98,14 +101,14 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun pinAuthMethod_tryAutoConfirm_withAutoConfirmPin() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene("container1")) + val currentScene by + collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) val message by collectLastValue(underTest.message) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234, autoConfirm = true) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAutoConfirmEnabled(true) utils.authenticationRepository.setUnlocked(false) - underTest.showOrUnlockDevice("container1") + underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN) underTest.clearMessage() @@ -128,14 +131,13 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun pinAuthMethod_tryAutoConfirm_withoutAutoConfirmPin() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene("container1")) + val currentScene by + collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) val message by collectLastValue(underTest.message) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234, autoConfirm = false) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - underTest.showOrUnlockDevice("container1") + underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.clearMessage() @@ -153,13 +155,14 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun passwordAuthMethod() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene("container1")) + val currentScene by + collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password("password") + AuthenticationMethodModel.Password ) utils.authenticationRepository.setUnlocked(false) - underTest.showOrUnlockDevice("container1") + underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD) @@ -185,13 +188,14 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun patternAuthMethod() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene("container1")) + val currentScene by + collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pattern(emptyList()) + AuthenticationMethodModel.Pattern ) utils.authenticationRepository.setUnlocked(false) - underTest.showOrUnlockDevice("container1") + underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN) @@ -204,7 +208,7 @@ class BouncerInteractorTest : SysuiTestCase() { // Wrong input. assertThat( underTest.authenticate( - listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(3, 4)) + listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(1, 2)) ) ) .isFalse() @@ -215,21 +219,20 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN) // Correct input. - assertThat(underTest.authenticate(emptyList())).isTrue() + assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN)).isTrue() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) } @Test fun showOrUnlockDevice_notLocked_switchesToGoneScene() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene("container1")) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234) - ) + val currentScene by + collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(true) runCurrent() - underTest.showOrUnlockDevice("container1") + underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) } @@ -237,11 +240,12 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun showOrUnlockDevice_authMethodNotSecure_switchesToGoneScene() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene("container1")) + val currentScene by + collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe) utils.authenticationRepository.setUnlocked(false) - underTest.showOrUnlockDevice("container1") + underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) } @@ -249,15 +253,16 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun showOrUnlockDevice_customMessageShown() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene("container1")) + val currentScene by + collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password("password") + AuthenticationMethodModel.Password ) utils.authenticationRepository.setUnlocked(false) val customMessage = "Hello there!" - underTest.showOrUnlockDevice("container1", customMessage) + underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1, customMessage) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) assertThat(message).isEqualTo(customMessage) @@ -266,50 +271,78 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun throttling() = testScope.runTest { + val isThrottled by collectLastValue(underTest.isThrottled) val throttling by collectLastValue(underTest.throttling) val message by collectLastValue(underTest.message) val currentScene by collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) runCurrent() underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1) runCurrent() assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer) - assertThat(throttling).isNull() + assertThat(isThrottled).isFalse() + assertThat(throttling).isEqualTo(AuthenticationThrottlingModel()) assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN) - repeat(BouncerInteractor.THROTTLE_EVERY) { times -> + repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { times -> // Wrong PIN. assertThat(underTest.authenticate(listOf(6, 7, 8, 9))).isFalse() - if (times < BouncerInteractor.THROTTLE_EVERY - 1) { + if ( + times < FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1 + ) { assertThat(message).isEqualTo(MESSAGE_WRONG_PIN) } } - assertThat(throttling).isNotNull() - assertTryAgainMessage(message, BouncerInteractor.THROTTLE_DURATION_SEC) + assertThat(isThrottled).isTrue() + assertThat(throttling) + .isEqualTo( + AuthenticationThrottlingModel( + failedAttemptCount = + FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING, + remainingMs = FakeAuthenticationRepository.THROTTLE_DURATION_MS, + ) + ) + assertTryAgainMessage( + message, + FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds + .toInt() + ) // Correct PIN, but throttled, so doesn't change away from the bouncer scene: - assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isFalse() + assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isNull() assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer) - assertTryAgainMessage(message, BouncerInteractor.THROTTLE_DURATION_SEC) + assertTryAgainMessage( + message, + FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds + .toInt() + ) - throttling?.totalDurationSec?.let { seconds -> + throttling?.remainingMs?.let { remainingMs -> + val seconds = ceil(remainingMs / 1000f).toInt() repeat(seconds) { time -> advanceTimeBy(1000) - val remainingTime = seconds - time - 1 - if (remainingTime > 0) { - assertTryAgainMessage(message, remainingTime) + val remainingTimeSec = seconds - time - 1 + if (remainingTimeSec > 0) { + assertTryAgainMessage(message, remainingTimeSec) } } } assertThat(message).isEqualTo("") - assertThat(throttling).isNull() + assertThat(isThrottled).isFalse() + assertThat(throttling) + .isEqualTo( + AuthenticationThrottlingModel( + failedAttemptCount = + FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING, + ) + ) assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer) // Correct PIN and no longer throttled so changes to the Gone scene: assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue() assertThat(currentScene?.key).isEqualTo(SceneKey.Gone) + assertThat(isThrottled).isFalse() + assertThat(throttling).isEqualTo(AuthenticationThrottlingModel()) } @Test 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 f811ce05e3149..0867558545bd3 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 @@ -55,10 +55,8 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() { @Test fun animateFailure() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234) - ) val animateFailure by collectLastValue(underTest.animateFailure) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(animateFailure).isFalse() // Wrong PIN: 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 5ffc47119e170..0356036fa78f1 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,8 @@ package com.android.systemui.bouncer.ui.viewmodel import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository import com.android.systemui.authentication.shared.model.AuthenticationMethodModel -import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils import com.google.common.truth.Truth.assertThat @@ -110,18 +110,16 @@ class BouncerViewModelTest : SysuiTestCase() { testScope.runTest { val message by collectLastValue(underTest.message) val throttling by collectLastValue(bouncerInteractor.throttling) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(message?.isUpdateAnimated).isTrue() - repeat(BouncerInteractor.THROTTLE_EVERY) { + repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { // Wrong PIN. bouncerInteractor.authenticate(listOf(3, 4, 5, 6)) } assertThat(message?.isUpdateAnimated).isFalse() - throttling?.totalDurationSec?.let { seconds -> advanceTimeBy(seconds * 1000L) } + throttling?.remainingMs?.let { remainingMs -> advanceTimeBy(remainingMs.toLong()) } assertThat(message?.isUpdateAnimated).isTrue() } @@ -135,18 +133,16 @@ class BouncerViewModelTest : SysuiTestCase() { } ) val throttling by collectLastValue(bouncerInteractor.throttling) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(isInputEnabled).isTrue() - repeat(BouncerInteractor.THROTTLE_EVERY) { + repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { // Wrong PIN. bouncerInteractor.authenticate(listOf(3, 4, 5, 6)) } assertThat(isInputEnabled).isFalse() - throttling?.totalDurationSec?.let { seconds -> advanceTimeBy(seconds * 1000L) } + throttling?.remainingMs?.let { milliseconds -> advanceTimeBy(milliseconds.toLong()) } assertThat(isInputEnabled).isTrue() } @@ -154,11 +150,9 @@ class BouncerViewModelTest : SysuiTestCase() { fun throttlingDialogMessage() = testScope.runTest { val throttlingDialogMessage by collectLastValue(underTest.throttlingDialogMessage) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - repeat(BouncerInteractor.THROTTLE_EVERY) { + repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { // Wrong PIN. assertThat(throttlingDialogMessage).isNull() bouncerInteractor.authenticate(listOf(3, 4, 5, 6)) @@ -173,11 +167,9 @@ class BouncerViewModelTest : SysuiTestCase() { return listOf( AuthenticationMethodModel.None, AuthenticationMethodModel.Swipe, - AuthenticationMethodModel.Pin(1234), - AuthenticationMethodModel.Password("password"), - AuthenticationMethodModel.Pattern( - listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(1, 1)) - ), + AuthenticationMethodModel.Pin, + AuthenticationMethodModel.Password, + AuthenticationMethodModel.Pattern, ) } } 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 699571b1537c3..b1533fecbc5ea 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 @@ -72,14 +72,18 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { @Test fun onShown() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME)) + val currentScene by + collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) val message by collectLastValue(bouncerViewModel.message) val password by collectLastValue(underTest.password) utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password("password") + AuthenticationMethodModel.Password ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer)) + sceneInteractor.setCurrentScene( + SceneTestUtils.CONTAINER_1, + SceneModel(SceneKey.Bouncer) + ) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() @@ -92,14 +96,18 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { @Test fun onPasswordInputChanged() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME)) + val currentScene by + collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) val message by collectLastValue(bouncerViewModel.message) val password by collectLastValue(underTest.password) utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password("password") + AuthenticationMethodModel.Password ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer)) + sceneInteractor.setCurrentScene( + SceneTestUtils.CONTAINER_1, + SceneModel(SceneKey.Bouncer) + ) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() runCurrent() @@ -114,12 +122,16 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateKeyPressed_whenCorrect() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME)) + val currentScene by + collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password("password") + AuthenticationMethodModel.Password ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer)) + sceneInteractor.setCurrentScene( + SceneTestUtils.CONTAINER_1, + SceneModel(SceneKey.Bouncer) + ) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onPasswordInputChanged("password") @@ -132,14 +144,18 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateKeyPressed_whenWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME)) + val currentScene by + collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) val message by collectLastValue(bouncerViewModel.message) val password by collectLastValue(underTest.password) utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password("password") + AuthenticationMethodModel.Password ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer)) + sceneInteractor.setCurrentScene( + SceneTestUtils.CONTAINER_1, + SceneModel(SceneKey.Bouncer) + ) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onPasswordInputChanged("wrong") @@ -154,14 +170,18 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateKeyPressed_correctAfterWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME)) + val currentScene by + collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) val message by collectLastValue(bouncerViewModel.message) val password by collectLastValue(underTest.password) utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password("password") + AuthenticationMethodModel.Password ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer)) + sceneInteractor.setCurrentScene( + SceneTestUtils.CONTAINER_1, + SceneModel(SceneKey.Bouncer) + ) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onPasswordInputChanged("wrong") @@ -180,7 +200,6 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { } companion object { - private const val CONTAINER_NAME = "container1" private const val ENTER_YOUR_PASSWORD = "Enter your password" private const val WRONG_PASSWORD = "Wrong password" } 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 9a1f584bced93..f69cbb8fd0040 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,6 +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.data.repository.FakeAuthenticationRepository import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils @@ -74,15 +75,19 @@ class PatternBouncerViewModelTest : SysuiTestCase() { @Test fun onShown() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME)) + val currentScene by + collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) val message by collectLastValue(bouncerViewModel.message) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pattern(CORRECT_PATTERN) + AuthenticationMethodModel.Pattern ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer)) + sceneInteractor.setCurrentScene( + SceneTestUtils.CONTAINER_1, + SceneModel(SceneKey.Bouncer) + ) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() @@ -96,15 +101,19 @@ class PatternBouncerViewModelTest : SysuiTestCase() { @Test fun onDragStart() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME)) + val currentScene by + collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) val message by collectLastValue(bouncerViewModel.message) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pattern(CORRECT_PATTERN) + AuthenticationMethodModel.Pattern ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer)) + sceneInteractor.setCurrentScene( + SceneTestUtils.CONTAINER_1, + SceneModel(SceneKey.Bouncer) + ) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() runCurrent() @@ -120,14 +129,18 @@ class PatternBouncerViewModelTest : SysuiTestCase() { @Test fun onDragEnd_whenCorrect() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME)) + val currentScene by + collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pattern(CORRECT_PATTERN) + AuthenticationMethodModel.Pattern ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer)) + sceneInteractor.setCurrentScene( + SceneTestUtils.CONTAINER_1, + SceneModel(SceneKey.Bouncer) + ) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onDragStart() @@ -167,15 +180,19 @@ class PatternBouncerViewModelTest : SysuiTestCase() { @Test fun onDragEnd_whenWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME)) + val currentScene by + collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) val message by collectLastValue(bouncerViewModel.message) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pattern(CORRECT_PATTERN) + AuthenticationMethodModel.Pattern ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer)) + sceneInteractor.setCurrentScene( + SceneTestUtils.CONTAINER_1, + SceneModel(SceneKey.Bouncer) + ) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onDragStart() @@ -199,15 +216,19 @@ class PatternBouncerViewModelTest : SysuiTestCase() { @Test fun onDragEnd_correctAfterWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME)) + val currentScene by + collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) val message by collectLastValue(bouncerViewModel.message) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pattern(CORRECT_PATTERN) + AuthenticationMethodModel.Pattern ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer)) + sceneInteractor.setCurrentScene( + SceneTestUtils.CONTAINER_1, + SceneModel(SceneKey.Bouncer) + ) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onDragStart() @@ -241,20 +262,8 @@ class PatternBouncerViewModelTest : SysuiTestCase() { } companion object { - private const val CONTAINER_NAME = "container1" private const val ENTER_YOUR_PATTERN = "Enter your pattern" private const val WRONG_PATTERN = "Wrong pattern" - private val CORRECT_PATTERN = - listOf( - AuthenticationMethodModel.Pattern.PatternCoordinate(x = 1, y = 1), - AuthenticationMethodModel.Pattern.PatternCoordinate(x = 0, y = 1), - AuthenticationMethodModel.Pattern.PatternCoordinate(x = 0, y = 0), - AuthenticationMethodModel.Pattern.PatternCoordinate(x = 1, y = 0), - AuthenticationMethodModel.Pattern.PatternCoordinate(x = 2, y = 0), - AuthenticationMethodModel.Pattern.PatternCoordinate(x = 2, y = 1), - AuthenticationMethodModel.Pattern.PatternCoordinate(x = 2, y = 2), - AuthenticationMethodModel.Pattern.PatternCoordinate(x = 1, y = 2), - AuthenticationMethodModel.Pattern.PatternCoordinate(x = 0, y = 2), - ) + private val CORRECT_PATTERN = FakeAuthenticationRepository.PATTERN } } 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 608a187a1bc96..7d6c4a1a1455f 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 @@ -63,7 +63,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { return bouncerInteractor } }, - containerName = CONTAINER_NAME, + containerName = SceneTestUtils.CONTAINER_1, ) private val underTest = PinBouncerViewModel( @@ -82,11 +82,15 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onShown() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME)) + val currentScene by + collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) val message by collectLastValue(bouncerViewModel.message) val entries by collectLastValue(underTest.pinEntries) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer)) + sceneInteractor.setCurrentScene( + SceneTestUtils.CONTAINER_1, + SceneModel(SceneKey.Bouncer) + ) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() @@ -99,14 +103,16 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onPinButtonClicked() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME)) + val currentScene by + collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) val message by collectLastValue(bouncerViewModel.message) val entries by collectLastValue(underTest.pinEntries) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer)) + sceneInteractor.setCurrentScene( + SceneTestUtils.CONTAINER_1, + SceneModel(SceneKey.Bouncer) + ) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() runCurrent() @@ -122,14 +128,16 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onBackspaceButtonClicked() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME)) + val currentScene by + collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) val message by collectLastValue(bouncerViewModel.message) val entries by collectLastValue(underTest.pinEntries) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer)) + sceneInteractor.setCurrentScene( + SceneTestUtils.CONTAINER_1, + SceneModel(SceneKey.Bouncer) + ) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() runCurrent() @@ -146,13 +154,15 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onPinEdit() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME)) + val currentScene by + collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) val entries by collectLastValue(underTest.pinEntries) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer)) + sceneInteractor.setCurrentScene( + SceneTestUtils.CONTAINER_1, + SceneModel(SceneKey.Bouncer) + ) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() @@ -172,14 +182,16 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onBackspaceButtonLongPressed() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME)) + val currentScene by + collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) val message by collectLastValue(bouncerViewModel.message) val entries by collectLastValue(underTest.pinEntries) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer)) + sceneInteractor.setCurrentScene( + SceneTestUtils.CONTAINER_1, + SceneModel(SceneKey.Bouncer) + ) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() runCurrent() @@ -198,12 +210,14 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateButtonClicked_whenCorrect() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME)) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234) - ) + val currentScene by + collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer)) + sceneInteractor.setCurrentScene( + SceneTestUtils.CONTAINER_1, + SceneModel(SceneKey.Bouncer) + ) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onPinButtonClicked(1) @@ -219,14 +233,16 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateButtonClicked_whenWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME)) + val currentScene by + collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) val message by collectLastValue(bouncerViewModel.message) val entries by collectLastValue(underTest.pinEntries) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer)) + sceneInteractor.setCurrentScene( + SceneTestUtils.CONTAINER_1, + SceneModel(SceneKey.Bouncer) + ) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onPinButtonClicked(1) @@ -245,14 +261,16 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateButtonClicked_correctAfterWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME)) + val currentScene by + collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) val message by collectLastValue(bouncerViewModel.message) val entries by collectLastValue(underTest.pinEntries) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer)) + sceneInteractor.setCurrentScene( + SceneTestUtils.CONTAINER_1, + SceneModel(SceneKey.Bouncer) + ) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onPinButtonClicked(1) @@ -280,12 +298,15 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAutoConfirm_whenCorrect() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME)) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234, autoConfirm = true) - ) + val currentScene by + collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer)) + utils.authenticationRepository.setAutoConfirmEnabled(true) + sceneInteractor.setCurrentScene( + SceneTestUtils.CONTAINER_1, + SceneModel(SceneKey.Bouncer) + ) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onPinButtonClicked(1) @@ -299,14 +320,17 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAutoConfirm_whenWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME)) + val currentScene by + collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) val message by collectLastValue(bouncerViewModel.message) val entries by collectLastValue(underTest.pinEntries) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234, autoConfirm = true) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer)) + utils.authenticationRepository.setAutoConfirmEnabled(true) + sceneInteractor.setCurrentScene( + SceneTestUtils.CONTAINER_1, + SceneModel(SceneKey.Bouncer) + ) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onPinButtonClicked(1) @@ -324,9 +348,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { testScope.runTest { val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234, autoConfirm = false) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(backspaceButtonAppearance).isEqualTo(ActionButtonAppearance.Shown) } @@ -335,9 +357,8 @@ class PinBouncerViewModelTest : SysuiTestCase() { fun backspaceButtonAppearance_withAutoConfirmButNoInput_isHidden() = testScope.runTest { val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234, autoConfirm = true) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAutoConfirmEnabled(true) assertThat(backspaceButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden) } @@ -346,9 +367,8 @@ class PinBouncerViewModelTest : SysuiTestCase() { fun backspaceButtonAppearance_withAutoConfirmAndInput_isShownQuiet() = testScope.runTest { val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234, autoConfirm = true) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAutoConfirmEnabled(true) underTest.onPinButtonClicked(1) @@ -360,9 +380,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { testScope.runTest { val confirmButtonAppearance by collectLastValue(underTest.confirmButtonAppearance) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234, autoConfirm = false) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Shown) } @@ -371,9 +389,8 @@ class PinBouncerViewModelTest : SysuiTestCase() { fun confirmButtonAppearance_withAutoConfirm_isHidden() = testScope.runTest { val confirmButtonAppearance by collectLastValue(underTest.confirmButtonAppearance) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234, autoConfirm = true) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAutoConfirmEnabled(true) assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden) } @@ -382,9 +399,8 @@ class PinBouncerViewModelTest : SysuiTestCase() { fun hintedPinLength_withoutAutoConfirm_isNull() = testScope.runTest { val hintedPinLength by collectLastValue(underTest.hintedPinLength) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234, autoConfirm = false) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAutoConfirmEnabled(false) assertThat(hintedPinLength).isNull() } @@ -393,9 +409,8 @@ class PinBouncerViewModelTest : SysuiTestCase() { fun hintedPinLength_withAutoConfirmPinLessThanSixDigits_isNull() = testScope.runTest { val hintedPinLength by collectLastValue(underTest.hintedPinLength) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(12345, autoConfirm = true) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAutoConfirmEnabled(true) assertThat(hintedPinLength).isNull() } @@ -404,9 +419,9 @@ class PinBouncerViewModelTest : SysuiTestCase() { fun hintedPinLength_withAutoConfirmPinExactlySixDigits_isSix() = testScope.runTest { val hintedPinLength by collectLastValue(underTest.hintedPinLength) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(123456, autoConfirm = true) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAutoConfirmEnabled(true) + utils.authenticationRepository.overrideCredential(listOf(1, 2, 3, 4, 5, 6)) assertThat(hintedPinLength).isEqualTo(6) } @@ -415,15 +430,13 @@ class PinBouncerViewModelTest : SysuiTestCase() { fun hintedPinLength_withAutoConfirmPinMoreThanSixDigits_isNull() = testScope.runTest { val hintedPinLength by collectLastValue(underTest.hintedPinLength) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234567, autoConfirm = true) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAutoConfirmEnabled(true) assertThat(hintedPinLength).isNull() } companion object { - private const val CONTAINER_NAME = "container1" private const val ENTER_YOUR_PIN = "Enter your pin" private const val WRONG_PIN = "Wrong pin" } 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 index abbdc3d55ac6e..8b36284b8620c 100644 --- 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 @@ -94,9 +94,7 @@ class LockscreenSceneInteractorTest : SysuiTestCase() { testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) utils.authenticationRepository.setUnlocked(false) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) underTest.dismissLockscreen() @@ -109,9 +107,7 @@ class LockscreenSceneInteractorTest : SysuiTestCase() { testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) utils.authenticationRepository.setUnlocked(true) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) underTest.dismissLockscreen() @@ -153,9 +149,7 @@ class LockscreenSceneInteractorTest : SysuiTestCase() { testScope.runTest { val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked) sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen)) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(isUnlocked).isFalse() sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone)) 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 ff4ec4b738bd1..161dd660671e2 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 @@ -73,7 +73,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { testScope.runTest { val lockButtonIcon by collectLastValue(underTest.lockButtonIcon) utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password("password") + AuthenticationMethodModel.Password ) utils.authenticationRepository.setUnlocked(false) @@ -86,7 +86,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { testScope.runTest { val lockButtonIcon by collectLastValue(underTest.lockButtonIcon) utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password("password") + AuthenticationMethodModel.Password ) utils.authenticationRepository.setUnlocked(true) @@ -108,9 +108,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { fun upTransitionSceneKey_swipeToUnlockedNotEnabled_bouncer() = testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer) @@ -120,9 +118,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { fun onLockButtonClicked_deviceLockedSecurely_switchesToBouncer() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) runCurrent() @@ -135,9 +131,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { fun onContentClicked_deviceUnlocked_switchesToGone() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(true) runCurrent() @@ -150,9 +144,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { fun onContentClicked_deviceLockedSecurely_switchesToBouncer() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) runCurrent() @@ -165,9 +157,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { fun onLockButtonClicked_deviceUnlocked_switchesToGone() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(true) runCurrent() 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 c85c8baf7ccbf..4eedc99839c6e 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 @@ -69,9 +69,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { fun onContentClicked_deviceUnlocked_switchesToGone() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(true) runCurrent() @@ -84,9 +82,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { fun onContentClicked_deviceLockedSecurely_switchesToBouncer() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt index de15c7711bb36..9ce378dd079f7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt @@ -24,6 +24,7 @@ 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.android.systemui.scene.shared.model.SceneTransitionModel import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest @@ -40,7 +41,7 @@ class SceneContainerRepositoryTest : SysuiTestCase() { @Test fun allSceneKeys() { val underTest = utils.fakeSceneContainerRepository() - assertThat(underTest.allSceneKeys("container1")) + assertThat(underTest.allSceneKeys(SceneTestUtils.CONTAINER_1)) .isEqualTo( listOf( SceneKey.QuickSettings, @@ -61,10 +62,10 @@ class SceneContainerRepositoryTest : SysuiTestCase() { @Test fun currentScene() = runTest { val underTest = utils.fakeSceneContainerRepository() - val currentScene by collectLastValue(underTest.currentScene("container1")) + val currentScene by collectLastValue(underTest.currentScene(SceneTestUtils.CONTAINER_1)) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) - underTest.setCurrentScene("container1", SceneModel(SceneKey.Shade)) + underTest.setCurrentScene(SceneTestUtils.CONTAINER_1, SceneModel(SceneKey.Shade)) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade)) } @@ -85,26 +86,26 @@ class SceneContainerRepositoryTest : SysuiTestCase() { val underTest = utils.fakeSceneContainerRepository( setOf( - utils.fakeSceneContainerConfig("container1"), + utils.fakeSceneContainerConfig(SceneTestUtils.CONTAINER_1), utils.fakeSceneContainerConfig( - "container2", + SceneTestUtils.CONTAINER_2, listOf(SceneKey.QuickSettings, SceneKey.Lockscreen) ), ) ) - underTest.setCurrentScene("container2", SceneModel(SceneKey.Shade)) + underTest.setCurrentScene(SceneTestUtils.CONTAINER_2, SceneModel(SceneKey.Shade)) } @Test fun isVisible() = runTest { val underTest = utils.fakeSceneContainerRepository() - val isVisible by collectLastValue(underTest.isVisible("container1")) + val isVisible by collectLastValue(underTest.isVisible(SceneTestUtils.CONTAINER_1)) assertThat(isVisible).isTrue() - underTest.setVisible("container1", false) + underTest.setVisible(SceneTestUtils.CONTAINER_1, false) assertThat(isVisible).isFalse() - underTest.setVisible("container1", true) + underTest.setVisible(SceneTestUtils.CONTAINER_1, true) assertThat(isVisible).isTrue() } @@ -124,13 +125,13 @@ class SceneContainerRepositoryTest : SysuiTestCase() { fun sceneTransitionProgress() = runTest { val underTest = utils.fakeSceneContainerRepository() val sceneTransitionProgress by - collectLastValue(underTest.sceneTransitionProgress("container1")) + collectLastValue(underTest.sceneTransitionProgress(SceneTestUtils.CONTAINER_1)) assertThat(sceneTransitionProgress).isEqualTo(1f) - underTest.setSceneTransitionProgress("container1", 0.1f) + underTest.setSceneTransitionProgress(SceneTestUtils.CONTAINER_1, 0.1f) assertThat(sceneTransitionProgress).isEqualTo(0.1f) - underTest.setSceneTransitionProgress("container1", 0.9f) + underTest.setSceneTransitionProgress(SceneTestUtils.CONTAINER_1, 0.9f) assertThat(sceneTransitionProgress).isEqualTo(0.9f) } @@ -139,4 +140,75 @@ class SceneContainerRepositoryTest : SysuiTestCase() { val underTest = utils.fakeSceneContainerRepository() underTest.sceneTransitionProgress("nonExistingContainer") } + + @Test + fun setSceneTransition() = runTest { + val underTest = + utils.fakeSceneContainerRepository( + setOf( + utils.fakeSceneContainerConfig(SceneTestUtils.CONTAINER_1), + utils.fakeSceneContainerConfig( + SceneTestUtils.CONTAINER_2, + listOf(SceneKey.QuickSettings, SceneKey.Lockscreen) + ), + ) + ) + val sceneTransition by + collectLastValue(underTest.sceneTransitions(SceneTestUtils.CONTAINER_2)) + assertThat(sceneTransition).isNull() + + underTest.setSceneTransition( + SceneTestUtils.CONTAINER_2, + SceneKey.Lockscreen, + SceneKey.QuickSettings + ) + assertThat(sceneTransition) + .isEqualTo( + SceneTransitionModel(from = SceneKey.Lockscreen, to = SceneKey.QuickSettings) + ) + } + + @Test(expected = IllegalStateException::class) + fun setSceneTransition_noSuchContainer_throws() { + val underTest = utils.fakeSceneContainerRepository() + underTest.setSceneTransition("nonExistingContainer", SceneKey.Lockscreen, SceneKey.Shade) + } + + @Test(expected = IllegalStateException::class) + fun setSceneTransition_noFromSceneInContainer_throws() { + val underTest = + utils.fakeSceneContainerRepository( + setOf( + utils.fakeSceneContainerConfig(SceneTestUtils.CONTAINER_1), + utils.fakeSceneContainerConfig( + SceneTestUtils.CONTAINER_2, + listOf(SceneKey.QuickSettings, SceneKey.Lockscreen) + ), + ) + ) + underTest.setSceneTransition( + SceneTestUtils.CONTAINER_2, + SceneKey.Shade, + SceneKey.Lockscreen + ) + } + + @Test(expected = IllegalStateException::class) + fun setSceneTransition_noToSceneInContainer_throws() { + val underTest = + utils.fakeSceneContainerRepository( + setOf( + utils.fakeSceneContainerConfig(SceneTestUtils.CONTAINER_1), + utils.fakeSceneContainerConfig( + SceneTestUtils.CONTAINER_2, + listOf(SceneKey.QuickSettings, SceneKey.Lockscreen) + ), + ) + ) + underTest.setSceneTransition( + SceneTestUtils.CONTAINER_2, + SceneKey.Shade, + SceneKey.Lockscreen + ) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt index ee4f6c23ca8a7..3050c4edd24f0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt @@ -24,6 +24,7 @@ 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.android.systemui.scene.shared.model.SceneTransitionModel import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest @@ -40,36 +41,63 @@ class SceneInteractorTest : SysuiTestCase() { @Test fun allSceneKeys() { - assertThat(underTest.allSceneKeys("container1")).isEqualTo(utils.fakeSceneKeys()) + assertThat(underTest.allSceneKeys(SceneTestUtils.CONTAINER_1)) + .isEqualTo(utils.fakeSceneKeys()) } @Test - fun sceneTransitions() = runTest { - val currentScene by collectLastValue(underTest.currentScene("container1")) + fun currentScene() = runTest { + val currentScene by collectLastValue(underTest.currentScene(SceneTestUtils.CONTAINER_1)) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) - underTest.setCurrentScene("container1", SceneModel(SceneKey.Shade)) + underTest.setCurrentScene(SceneTestUtils.CONTAINER_1, SceneModel(SceneKey.Shade)) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade)) } @Test fun sceneTransitionProgress() = runTest { - val progress by collectLastValue(underTest.sceneTransitionProgress("container1")) + val progress by + collectLastValue(underTest.sceneTransitionProgress(SceneTestUtils.CONTAINER_1)) assertThat(progress).isEqualTo(1f) - underTest.setSceneTransitionProgress("container1", 0.55f) + underTest.setSceneTransitionProgress(SceneTestUtils.CONTAINER_1, 0.55f) assertThat(progress).isEqualTo(0.55f) } @Test fun isVisible() = runTest { - val isVisible by collectLastValue(underTest.isVisible("container1")) + val isVisible by collectLastValue(underTest.isVisible(SceneTestUtils.CONTAINER_1)) assertThat(isVisible).isTrue() - underTest.setVisible("container1", false) + underTest.setVisible(SceneTestUtils.CONTAINER_1, false) assertThat(isVisible).isFalse() - underTest.setVisible("container1", true) + underTest.setVisible(SceneTestUtils.CONTAINER_1, true) assertThat(isVisible).isTrue() } + + @Test + fun sceneTransitions() = runTest { + val transitions by collectLastValue(underTest.sceneTransitions(SceneTestUtils.CONTAINER_1)) + assertThat(transitions).isNull() + + val initialSceneKey = underTest.currentScene(SceneTestUtils.CONTAINER_1).value.key + underTest.setCurrentScene(SceneTestUtils.CONTAINER_1, SceneModel(SceneKey.Shade)) + assertThat(transitions) + .isEqualTo( + SceneTransitionModel( + from = initialSceneKey, + to = SceneKey.Shade, + ) + ) + + underTest.setCurrentScene(SceneTestUtils.CONTAINER_1, SceneModel(SceneKey.QuickSettings)) + assertThat(transitions) + .isEqualTo( + SceneTransitionModel( + from = SceneKey.Shade, + to = SceneKey.QuickSettings, + ) + ) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt index cd2f5af592cfb..6882be7fe184d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt @@ -40,7 +40,7 @@ class SceneContainerViewModelTest : SysuiTestCase() { private val underTest = SceneContainerViewModel( interactor = interactor, - containerName = "container1", + containerName = SceneTestUtils.CONTAINER_1, ) @Test @@ -48,10 +48,10 @@ class SceneContainerViewModelTest : SysuiTestCase() { val isVisible by collectLastValue(underTest.isVisible) assertThat(isVisible).isTrue() - interactor.setVisible("container1", false) + interactor.setVisible(SceneTestUtils.CONTAINER_1, false) assertThat(isVisible).isFalse() - interactor.setVisible("container1", true) + interactor.setVisible(SceneTestUtils.CONTAINER_1, true) assertThat(isVisible).isTrue() } 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 5d2d192bb61a3..309ab058fb64c 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 @@ -70,9 +70,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { fun upTransitionSceneKey_deviceLocked_lockScreen() = testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Lockscreen) @@ -82,9 +80,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { fun upTransitionSceneKey_deviceUnlocked_gone() = testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(true) assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone) @@ -94,9 +90,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { fun onContentClicked_deviceUnlocked_switchesToGone() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(true) runCurrent() @@ -109,9 +103,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { fun onContentClicked_deviceLockedSecurely_switchesToBouncer() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin(1234) - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) runCurrent() 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 a718f70b235d0..bebdd4004c3dc 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 @@ -16,40 +16,154 @@ package com.android.systemui.authentication.data.repository +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.shared.model.AuthenticationResultModel +import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow class FakeAuthenticationRepository( - private val delegate: AuthenticationRepository, - private val onSecurityModeChanged: (SecurityMode) -> Unit, -) : AuthenticationRepository by delegate { + private val currentTime: () -> Long, +) : AuthenticationRepository { + + private val _isBypassEnabled = MutableStateFlow(false) + override val isBypassEnabled: StateFlow = _isBypassEnabled + + private val _isAutoConfirmEnabled = MutableStateFlow(false) + override val isAutoConfirmEnabled: StateFlow = _isAutoConfirmEnabled.asStateFlow() private val _isUnlocked = MutableStateFlow(false) override val isUnlocked: StateFlow = _isUnlocked.asStateFlow() - private var authenticationMethod: AuthenticationMethodModel = DEFAULT_AUTHENTICATION_METHOD + override val hintedPinLength: Int = 6 + + private val _isPatternVisible = MutableStateFlow(true) + override val isPatternVisible: StateFlow = _isPatternVisible.asStateFlow() + + private val _throttling = MutableStateFlow(AuthenticationThrottlingModel()) + override val throttling: StateFlow = _throttling.asStateFlow() + + private val _authenticationMethod = + MutableStateFlow(DEFAULT_AUTHENTICATION_METHOD) + val authenticationMethod: StateFlow = + _authenticationMethod.asStateFlow() + + private var isLockscreenEnabled = true + private var failedAttemptCount = 0 + private var throttlingEndTimestamp = 0L + private var credentialOverride: List? = null + private var securityMode: SecurityMode = DEFAULT_AUTHENTICATION_METHOD.toSecurityMode() override suspend fun getAuthenticationMethod(): AuthenticationMethodModel { - return authenticationMethod + return authenticationMethod.value } fun setAuthenticationMethod(authenticationMethod: AuthenticationMethodModel) { - this.authenticationMethod = authenticationMethod - onSecurityModeChanged(authenticationMethod.toSecurityMode()) + _authenticationMethod.value = authenticationMethod + securityMode = authenticationMethod.toSecurityMode() + } + + fun overrideCredential(pin: List) { + credentialOverride = pin + } + + override suspend fun isLockscreenEnabled(): Boolean { + return isLockscreenEnabled + } + + override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) { + failedAttemptCount = if (isSuccessful) 0 else failedAttemptCount + 1 + _isUnlocked.value = isSuccessful + } + + override suspend fun getPinLength(): Int { + return (credentialOverride ?: listOf(1, 2, 3, 4)).size + } + + override fun setBypassEnabled(isBypassEnabled: Boolean) { + _isBypassEnabled.value = isBypassEnabled + } + + override suspend fun getFailedAuthenticationAttemptCount(): Int { + return failedAttemptCount + } + + override suspend fun getThrottlingEndTimestamp(): Long { + return throttlingEndTimestamp + } + + override fun setThrottling(throttlingModel: AuthenticationThrottlingModel) { + _throttling.value = throttlingModel } fun setUnlocked(isUnlocked: Boolean) { _isUnlocked.value = isUnlocked } - companion object { - val DEFAULT_AUTHENTICATION_METHOD = - AuthenticationMethodModel.Pin(listOf(1, 2, 3, 4), autoConfirm = false) + fun setAutoConfirmEnabled(isEnabled: Boolean) { + _isAutoConfirmEnabled.value = isEnabled + } - fun AuthenticationMethodModel.toSecurityMode(): SecurityMode { + fun setLockscreenEnabled(isLockscreenEnabled: Boolean) { + this.isLockscreenEnabled = isLockscreenEnabled + } + + override suspend fun setThrottleDuration(durationMs: Int) { + throttlingEndTimestamp = if (durationMs > 0) currentTime() + durationMs else 0 + } + + override suspend fun checkCredential( + credential: LockscreenCredential + ): AuthenticationResultModel { + val expectedCredential = credentialOverride ?: getExpectedCredential(securityMode) + val isSuccessful = + when { + credential.type != getCurrentCredentialType(securityMode) -> false + credential.type == LockPatternUtils.CREDENTIAL_TYPE_PIN -> + credential.isPin && credential.matches(expectedCredential) + credential.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD -> + credential.isPassword && credential.matches(expectedCredential) + credential.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN -> + credential.isPattern && credential.matches(expectedCredential) + else -> error("Unexpected credential type ${credential.type}!") + } + + return if ( + isSuccessful || failedAttemptCount < MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1 + ) { + AuthenticationResultModel( + isSuccessful = isSuccessful, + throttleDurationMs = 0, + ) + } else { + AuthenticationResultModel( + isSuccessful = false, + throttleDurationMs = THROTTLE_DURATION_MS, + ) + } + } + + companion object { + 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), + ) + const val MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING = 5 + const val THROTTLE_DURATION_MS = 30000 + + private fun AuthenticationMethodModel.toSecurityMode(): SecurityMode { return when (this) { is AuthenticationMethodModel.Pin -> SecurityMode.PIN is AuthenticationMethodModel.Password -> SecurityMode.Password @@ -58,5 +172,50 @@ class FakeAuthenticationRepository( is AuthenticationMethodModel.None -> SecurityMode.None } } + + @LockPatternUtils.CredentialType + private fun getCurrentCredentialType( + securityMode: SecurityMode, + ): Int { + return when (securityMode) { + SecurityMode.PIN, + SecurityMode.SimPin, + SecurityMode.SimPuk -> LockPatternUtils.CREDENTIAL_TYPE_PIN + SecurityMode.Password -> LockPatternUtils.CREDENTIAL_TYPE_PASSWORD + SecurityMode.Pattern -> LockPatternUtils.CREDENTIAL_TYPE_PATTERN + SecurityMode.None -> LockPatternUtils.CREDENTIAL_TYPE_NONE + else -> error("Unsupported SecurityMode $securityMode!") + } + } + + private fun getExpectedCredential(securityMode: SecurityMode): List { + return when (val credentialType = getCurrentCredentialType(securityMode)) { + LockPatternUtils.CREDENTIAL_TYPE_PIN -> listOf(1, 2, 3, 4) + LockPatternUtils.CREDENTIAL_TYPE_PASSWORD -> "password".toList() + LockPatternUtils.CREDENTIAL_TYPE_PATTERN -> PATTERN.toCells() + else -> error("Unsupported credential type $credentialType!") + } + } + + private fun LockscreenCredential.matches(expectedCredential: List): Boolean { + @Suppress("UNCHECKED_CAST") + return when { + isPin -> + credential.map { byte -> byte.toInt().toChar() - '0' } == expectedCredential + isPassword -> credential.map { byte -> byte.toInt().toChar() } == expectedCredential + isPattern -> + credential.contentEquals( + LockPatternUtils.patternToByteArray( + expectedCredential as List + ) + ) + else -> error("Unsupported credential type $type!") + } + } + + 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/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index f6cbb072495f4..3e8f6d752cfc9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -187,6 +187,10 @@ class FakeKeyguardRepository : KeyguardRepository { _statusBarState.value = state } + fun setKeyguardUnlocked(isUnlocked: Boolean) { + _isKeyguardUnlocked.value = isUnlocked + } + override fun isUdfpsSupported(): Boolean { return _isUdfpsSupported.value } 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 0b6e2a2a4e51b..fec1187d8d110 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 @@ -16,29 +16,30 @@ package com.android.systemui.scene -import com.android.keyguard.KeyguardSecurityModel.SecurityMode +import android.content.pm.UserInfo import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.AuthenticationRepository -import com.android.systemui.authentication.data.repository.AuthenticationRepositoryImpl import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository -import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository.Companion.toSecurityMode import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.bouncer.data.repository.BouncerRepository import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor import com.android.systemui.scene.data.repository.SceneContainerRepository import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.SceneContainerConfig +import com.android.systemui.scene.shared.model.SceneContainerNames import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.TestDispatcher import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.currentTime /** * Utilities for creating scene container framework related repositories, interactors, and @@ -48,22 +49,19 @@ import kotlinx.coroutines.test.TestScope class SceneTestUtils( test: SysuiTestCase, ) { - val testDispatcher: TestDispatcher by lazy { StandardTestDispatcher() } - val testScope: TestScope by lazy { TestScope(testDispatcher) } - private var securityMode: SecurityMode = - FakeAuthenticationRepository.DEFAULT_AUTHENTICATION_METHOD.toSecurityMode() + val testDispatcher = StandardTestDispatcher() + val testScope = TestScope(testDispatcher) + private val userRepository: UserRepository by lazy { + FakeUserRepository().apply { + val users = listOf(UserInfo(/* id= */ 0, "name", /* flags= */ 0)) + setUserInfos(users) + runBlocking { setSelectedUserInfo(users.first()) } + } + } + val authenticationRepository: FakeAuthenticationRepository by lazy { FakeAuthenticationRepository( - delegate = - AuthenticationRepositoryImpl( - applicationScope = applicationScope(), - getSecurityMode = { securityMode }, - backgroundDispatcher = testDispatcher, - userRepository = FakeUserRepository(), - lockPatternUtils = mock(), - keyguardRepository = FakeKeyguardRepository(), - ), - onSecurityModeChanged = { securityMode = it }, + currentTime = { testScope.currentTime }, ) } private val context = test.context @@ -105,7 +103,7 @@ class SceneTestUtils( ) } - fun authenticationRepository(): AuthenticationRepository { + fun authenticationRepository(): FakeAuthenticationRepository { return authenticationRepository } @@ -115,6 +113,9 @@ class SceneTestUtils( return AuthenticationInteractor( applicationScope = applicationScope(), repository = repository, + backgroundDispatcher = testDispatcher, + userRepository = userRepository, + clock = mock { whenever(elapsedRealtime()).thenAnswer { testScope.currentTime } } ) } @@ -172,7 +173,7 @@ class SceneTestUtils( } companion object { - const val CONTAINER_1 = "container1" + const val CONTAINER_1 = SceneContainerNames.SYSTEM_UI_DEFAULT const val CONTAINER_2 = "container2" } }