diff options
4 files changed, 94 insertions, 26 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt index 661c3458574e..2a02164224a4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt @@ -313,6 +313,59 @@ class AuthenticationInteractorTest : SysuiTestCase() { } @Test + fun isAutoConfirmEnabled_featureDisabled_returnsFalse() = + testScope.runTest { + val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) + utils.authenticationRepository.setAutoConfirmFeatureEnabled(false) + + assertThat(isAutoConfirmEnabled).isFalse() + } + + @Test + fun isAutoConfirmEnabled_featureEnabled_returnsTrue() = + testScope.runTest { + val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) + utils.authenticationRepository.setAutoConfirmFeatureEnabled(true) + + assertThat(isAutoConfirmEnabled).isTrue() + } + + @Test + fun isAutoConfirmEnabled_featureEnabledButDisabledByThrottling() = + testScope.runTest { + val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) + val throttling by collectLastValue(underTest.throttling) + utils.authenticationRepository.setAutoConfirmFeatureEnabled(true) + + // The feature is enabled. + assertThat(isAutoConfirmEnabled).isTrue() + + // Make many wrong attempts to trigger throttling. + repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { + underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN + } + assertThat(throttling).isNotNull() + + // Throttling disabled auto-confirm. + assertThat(isAutoConfirmEnabled).isFalse() + + // Move the clock forward one more second, to completely finish the throttling period: + advanceTimeBy(FakeAuthenticationRepository.THROTTLE_DURATION_MS + 1000L) + assertThat(throttling).isNull() + + // Auto-confirm is still disabled, because throttling occurred at least once in this + // session. + assertThat(isAutoConfirmEnabled).isFalse() + + // Correct PIN and unlocks successfully, resetting the 'session'. + assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) + .isEqualTo(AuthenticationResult.SUCCEEDED) + + // Auto-confirm is re-enabled. + assertThat(isAutoConfirmEnabled).isTrue() + } + + @Test fun throttling() = testScope.runTest { val throttling by collectLastValue(underTest.throttling) @@ -350,6 +403,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { ) // Move the clock forward to ALMOST skip the throttling, leaving one second to go: + val throttleTimeoutSec = FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS repeat(FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS - 1) { time -> advanceTimeBy(1000) assertThat(throttling) @@ -358,8 +412,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { failedAttemptCount = FakeAuthenticationRepository .MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING, - remainingSeconds = - FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS - (time + 1), + remainingSeconds = throttleTimeoutSec - (time + 1), ) ) } diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt index dd4ca92fcc02..bd84b28ab4d7 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt @@ -60,14 +60,6 @@ import kotlinx.coroutines.withContext /** Defines interface for classes that can access authentication-related application state. */ interface AuthenticationRepository { /** - * Whether the auto confirm feature is enabled for the currently-selected user. - * - * Note that the length of the PIN is also important to take into consideration, please see - * [hintedPinLength]. - */ - val isAutoConfirmFeatureEnabled: StateFlow<Boolean> - - /** * Emits the result whenever a PIN/Pattern/Password security challenge is attempted by the user * in order to unlock the device. */ @@ -93,6 +85,17 @@ interface AuthenticationRepository { */ val throttling: MutableStateFlow<AuthenticationThrottlingModel?> + /** Whether throttling has occurred at least once since the last successful authentication. */ + val hasThrottlingOccurred: MutableStateFlow<Boolean> + + /** + * Whether the auto confirm feature is enabled for the currently-selected user. + * + * Note that the length of the PIN is also important to take into consideration, please see + * [hintedPinLength]. + */ + val isAutoConfirmFeatureEnabled: StateFlow<Boolean> + /** * The currently-configured authentication method. This determines how the authentication * challenge needs to be completed in order to unlock an otherwise locked device. @@ -172,11 +175,6 @@ constructor( mobileConnectionsRepository: MobileConnectionsRepository, ) : AuthenticationRepository { - override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> = - refreshingFlow( - initialValue = false, - getFreshValue = lockPatternUtils::isAutoPinConfirmEnabled, - ) override val authenticationChallengeResult = MutableSharedFlow<Boolean>() override val hintedPinLength: Int = 6 @@ -190,8 +188,13 @@ constructor( override val throttling: MutableStateFlow<AuthenticationThrottlingModel?> = MutableStateFlow(null) - private val selectedUserId: Int - get() = userRepository.getSelectedUserInfo().id + override val hasThrottlingOccurred: MutableStateFlow<Boolean> = MutableStateFlow(false) + + override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> = + refreshingFlow( + initialValue = false, + getFreshValue = lockPatternUtils::isAutoPinConfirmEnabled, + ) override val authenticationMethod: Flow<AuthenticationMethodModel> = combine(userRepository.selectedUserInfo, mobileConnectionsRepository.isAnySimSecure) { @@ -280,6 +283,9 @@ constructor( } } + private val selectedUserId: Int + get() = userRepository.getSelectedUserInfo().id + /** * Returns a [StateFlow] that's automatically kept fresh. The passed-in [getFreshValue] is * invoked on a background thread every time the selected user is changed and every time a new 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 4e67771cba82..7f8f8875ae98 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 @@ -95,15 +95,14 @@ constructor( * * Note that the length of the PIN is also important to take into consideration, please see * [hintedPinLength]. - * - * During throttling, this is always disabled (`false`). */ val isAutoConfirmEnabled: StateFlow<Boolean> = - combine(repository.isAutoConfirmFeatureEnabled, repository.throttling) { + combine(repository.isAutoConfirmFeatureEnabled, repository.hasThrottlingOccurred) { featureEnabled, - throttling -> - // Disable auto-confirm during throttling. - featureEnabled && throttling == null + hasThrottlingOccurred -> + // Disable auto-confirm if throttling occurred since the last successful + // authentication attempt. + featureEnabled && !hasThrottlingOccurred } .stateIn( scope = applicationScope, @@ -221,6 +220,7 @@ constructor( repository.setThrottleDuration( durationMs = authenticationResult.throttleDurationMs, ) + repository.hasThrottlingOccurred.value = true startThrottlingCountdown() } @@ -228,6 +228,8 @@ constructor( // Since authentication succeeded, we should refresh throttling to make sure that our // state is completely reflecting the upstream source of truth. refreshThrottling() + + repository.hasThrottlingOccurred.value = false } return if (authenticationResult.isSuccessful) { 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 03270870bccd..4642b470fd2d 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 @@ -40,9 +40,6 @@ class FakeAuthenticationRepository( private val currentTime: () -> Long, ) : AuthenticationRepository { - private val _isAutoConfirmFeatureEnabled = MutableStateFlow(false) - override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> = - _isAutoConfirmFeatureEnabled.asStateFlow() override val authenticationChallengeResult = MutableSharedFlow<Boolean>() override val hintedPinLength: Int = HINTING_PIN_LENGTH @@ -53,6 +50,12 @@ class FakeAuthenticationRepository( override val throttling: MutableStateFlow<AuthenticationThrottlingModel?> = MutableStateFlow(null) + override val hasThrottlingOccurred = MutableStateFlow(false) + + private val _isAutoConfirmFeatureEnabled = MutableStateFlow(false) + override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> = + _isAutoConfirmFeatureEnabled.asStateFlow() + private val _authenticationMethod = MutableStateFlow<AuthenticationMethodModel>(DEFAULT_AUTHENTICATION_METHOD) override val authenticationMethod: StateFlow<AuthenticationMethodModel> = @@ -107,6 +110,9 @@ class FakeAuthenticationRepository( override suspend fun setThrottleDuration(durationMs: Int) { throttlingEndTimestamp = if (durationMs > 0) currentTime() + durationMs else 0 + if (durationMs > 0) { + hasThrottlingOccurred.value = true + } } override suspend fun checkCredential( @@ -128,6 +134,7 @@ class FakeAuthenticationRepository( return if ( isSuccessful || failedAttemptCount < MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1 ) { + hasThrottlingOccurred.value = false AuthenticationResultModel( isSuccessful = isSuccessful, throttleDurationMs = 0, |