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,