Loading packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt +55 −2 Original line number Diff line number Diff line Loading @@ -312,6 +312,59 @@ class AuthenticationInteractorTest : SysuiTestCase() { .isEqualTo(AuthenticationResult.SKIPPED) } @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 { Loading Loading @@ -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) Loading @@ -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), ) ) } Loading packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt +21 −15 Original line number Diff line number Diff line Loading @@ -59,14 +59,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. Loading @@ -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. Loading Loading @@ -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 Loading @@ -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) { Loading Loading @@ -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 Loading packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt +8 −6 Original line number Diff line number Diff line Loading @@ -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, Loading Loading @@ -221,6 +220,7 @@ constructor( repository.setThrottleDuration( durationMs = authenticationResult.throttleDurationMs, ) repository.hasThrottlingOccurred.value = true startThrottlingCountdown() } Loading @@ -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) { Loading packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt +10 −3 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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> = Loading Loading @@ -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( Loading @@ -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, Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt +55 −2 Original line number Diff line number Diff line Loading @@ -312,6 +312,59 @@ class AuthenticationInteractorTest : SysuiTestCase() { .isEqualTo(AuthenticationResult.SKIPPED) } @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 { Loading Loading @@ -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) Loading @@ -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), ) ) } Loading
packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt +21 −15 Original line number Diff line number Diff line Loading @@ -59,14 +59,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. Loading @@ -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. Loading Loading @@ -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 Loading @@ -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) { Loading Loading @@ -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 Loading
packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt +8 −6 Original line number Diff line number Diff line Loading @@ -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, Loading Loading @@ -221,6 +220,7 @@ constructor( repository.setThrottleDuration( durationMs = authenticationResult.throttleDurationMs, ) repository.hasThrottlingOccurred.value = true startThrottlingCountdown() } Loading @@ -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) { Loading
packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt +10 −3 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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> = Loading Loading @@ -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( Loading @@ -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, Loading