Merge "[flexiglass] Throttling disables auto-confirm for rest of the session." into main
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 661c345..2a02164 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 @@
}
@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 @@
)
// 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 @@
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 dd4ca92..bd84b28 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 @@
/** 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 @@
*/
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 @@
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 @@
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 @@
}
}
+ 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 4e67771..7f8f887 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 @@
*
* 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 @@
repository.setThrottleDuration(
durationMs = authenticationResult.throttleDurationMs,
)
+ repository.hasThrottlingOccurred.value = true
startThrottlingCountdown()
}
@@ -228,6 +228,8 @@
// 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 0327087..4642b47 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 @@
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 @@
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 @@
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 @@
return if (
isSuccessful || failedAttemptCount < MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1
) {
+ hasThrottlingOccurred.value = false
AuthenticationResultModel(
isSuccessful = isSuccessful,
throttleDurationMs = 0,