Loading packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt +22 −35 Original line number Diff line number Diff line Loading @@ -76,19 +76,21 @@ class AuthenticationInteractorTest : SysuiTestCase() { } @Test fun authenticate_withCorrectPin_returnsTrue() = fun authenticate_withCorrectPin_succeeds() = testScope.runTest { val isThrottled by collectLastValue(underTest.isThrottled) val throttling by collectLastValue(underTest.throttling) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) .isEqualTo(AuthenticationResult.SUCCEEDED) assertThat(isThrottled).isFalse() assertThat(throttling).isNull() } @Test fun authenticate_withIncorrectPin_returnsFalse() = fun authenticate_withIncorrectPin_fails() = testScope.runTest { utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4))) .isEqualTo(AuthenticationResult.FAILED) } Loading @@ -101,7 +103,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { } @Test fun authenticate_withCorrectMaxLengthPin_returnsTrue() = fun authenticate_withCorrectMaxLengthPin_succeeds() = testScope.runTest { val pin = List(16) { 9 } utils.authenticationRepository.apply { Loading @@ -113,10 +115,10 @@ class AuthenticationInteractorTest : SysuiTestCase() { } @Test fun authenticate_withCorrectTooLongPin_returnsFalse() = fun authenticate_withCorrectTooLongPin_fails() = testScope.runTest { // Max pin length is 16 digits. To avoid issues with overflows, this test ensures // that all pins > 16 decimal digits are rejected. // Max pin length is 16 digits. To avoid issues with overflows, this test ensures that // all pins > 16 decimal digits are rejected. // If the policy changes, there is work to do in SysUI. assertThat(DevicePolicyManager.MAX_PASSWORD_LENGTH).isLessThan(17) Loading @@ -127,20 +129,20 @@ class AuthenticationInteractorTest : SysuiTestCase() { } @Test fun authenticate_withCorrectPassword_returnsTrue() = fun authenticate_withCorrectPassword_succeeds() = testScope.runTest { val isThrottled by collectLastValue(underTest.isThrottled) val throttling by collectLastValue(underTest.throttling) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) assertThat(underTest.authenticate("password".toList())) .isEqualTo(AuthenticationResult.SUCCEEDED) assertThat(isThrottled).isFalse() assertThat(throttling).isNull() } @Test fun authenticate_withIncorrectPassword_returnsFalse() = fun authenticate_withIncorrectPassword_fails() = testScope.runTest { utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password Loading @@ -151,7 +153,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { } @Test fun authenticate_withCorrectPattern_returnsTrue() = fun authenticate_withCorrectPattern_succeeds() = testScope.runTest { utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pattern Loading @@ -162,7 +164,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { } @Test fun authenticate_withIncorrectPattern_returnsFalse() = fun authenticate_withIncorrectPattern_fails() = testScope.runTest { utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pattern Loading @@ -185,7 +187,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNull() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) val isThrottled by collectLastValue(underTest.isThrottled) val throttling by collectLastValue(underTest.throttling) utils.authenticationRepository.apply { setAuthenticationMethod(AuthenticationMethodModel.Pin) setAutoConfirmFeatureEnabled(true) Loading @@ -201,7 +203,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { ) ) .isEqualTo(AuthenticationResult.SKIPPED) assertThat(isThrottled).isFalse() assertThat(throttling).isNull() } @Test Loading Loading @@ -316,22 +318,18 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun throttling() = testScope.runTest { val throttling by collectLastValue(underTest.throttling) val isThrottled by collectLastValue(underTest.isThrottled) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN) assertThat(isThrottled).isFalse() assertThat(throttling).isEqualTo(AuthenticationThrottlingModel()) assertThat(throttling).isNull() // 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(isThrottled).isFalse() assertThat(throttling).isEqualTo(AuthenticationThrottlingModel()) assertThat(throttling).isNull() } // Make one more wrong attempt, leading to throttling: underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN assertThat(isThrottled).isTrue() assertThat(throttling) .isEqualTo( AuthenticationThrottlingModel( Loading @@ -344,7 +342,6 @@ class AuthenticationInteractorTest : SysuiTestCase() { // Correct PIN, but throttled, so doesn't attempt it: assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) .isEqualTo(AuthenticationResult.SKIPPED) assertThat(isThrottled).isTrue() assertThat(throttling) .isEqualTo( AuthenticationThrottlingModel( Loading @@ -360,7 +357,6 @@ class AuthenticationInteractorTest : SysuiTestCase() { .toInt() repeat(throttleTimeoutSec - 1) { time -> advanceTimeBy(1000) assertThat(isThrottled).isTrue() assertThat(throttling) .isEqualTo( AuthenticationThrottlingModel( Loading @@ -376,21 +372,12 @@ class AuthenticationInteractorTest : SysuiTestCase() { // Move the clock forward one more second, to completely finish the throttling period: advanceTimeBy(1000) assertThat(isThrottled).isFalse() assertThat(throttling) .isEqualTo( AuthenticationThrottlingModel( failedAttemptCount = FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING, remainingMs = 0, ) ) assertThat(throttling).isNull() // Correct PIN and no longer throttled so unlocks successfully: assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) .isEqualTo(AuthenticationResult.SUCCEEDED) assertThat(isThrottled).isFalse() assertThat(throttling).isEqualTo(AuthenticationThrottlingModel()) assertThat(throttling).isNull() } @Test Loading packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt +3 −14 Original line number Diff line number Diff line Loading @@ -249,12 +249,10 @@ 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) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(isThrottled).isFalse() assertThat(throttling).isEqualTo(AuthenticationThrottlingModel()) assertThat(throttling).isNull() repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { times -> // Wrong PIN. assertThat(underTest.authenticate(listOf(6, 7, 8, 9))) Loading @@ -265,7 +263,6 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(message).isEqualTo(MESSAGE_WRONG_PIN) } } assertThat(isThrottled).isTrue() assertThat(throttling) .isEqualTo( AuthenticationThrottlingModel( Loading Loading @@ -300,20 +297,12 @@ class BouncerInteractorTest : SysuiTestCase() { } } assertThat(message).isEqualTo("") assertThat(isThrottled).isFalse() assertThat(throttling) .isEqualTo( AuthenticationThrottlingModel( failedAttemptCount = FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING, ) ) assertThat(throttling).isNull() // Correct PIN and no longer throttled so changes to the Gone scene: assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) .isEqualTo(AuthenticationResult.SUCCEEDED) assertThat(isThrottled).isFalse() assertThat(throttling).isEqualTo(AuthenticationThrottlingModel()) assertThat(throttling).isNull() } @Test Loading packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt +2 −8 Original line number Diff line number Diff line Loading @@ -337,20 +337,14 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { } val remainingTimeMs = 30_000 authenticationRepository.setThrottleDuration(remainingTimeMs) authenticationRepository.setThrottling( authenticationRepository.throttling.value = AuthenticationThrottlingModel( failedAttemptCount = failedAttemptCount, remainingMs = remainingTimeMs, ) ) } else { authenticationRepository.reportAuthenticationAttempt(true) authenticationRepository.setThrottling( AuthenticationThrottlingModel( failedAttemptCount = failedAttemptCount, remainingMs = 0, ) ) authenticationRepository.throttling.value = null } runCurrent() Loading packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt +8 −12 Original line number Diff line number Diff line Loading @@ -80,7 +80,7 @@ interface AuthenticationRepository { * The exact length a PIN should be for us to enable PIN length hinting. * * A PIN that's shorter or longer than this is not eligible for the UI to render hints showing * how many digits the current PIN is, even if [isAutoConfirmEnabled] is enabled. * how many digits the current PIN is, even if [isAutoConfirmFeatureEnabled] is enabled. * * Note that PIN length hinting is only available if the PIN auto confirmation feature is * available. Loading @@ -90,8 +90,11 @@ interface AuthenticationRepository { /** Whether the pattern should be visible for the currently-selected user. */ val isPatternVisible: StateFlow<Boolean> /** The current throttling state, as cached via [setThrottling]. */ val throttling: StateFlow<AuthenticationThrottlingModel> /** * The current authentication throttling state, set when the user has to wait before being able * to try another authentication attempt. `null` indicates throttling isn't active. */ val throttling: MutableStateFlow<AuthenticationThrottlingModel?> /** * The currently-configured authentication method. This determines how the authentication Loading Loading @@ -146,9 +149,6 @@ interface AuthenticationRepository { */ 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). Loading Loading @@ -190,8 +190,8 @@ constructor( getFreshValue = lockPatternUtils::isVisiblePatternEnabled, ) private val _throttling = MutableStateFlow(AuthenticationThrottlingModel()) override val throttling: StateFlow<AuthenticationThrottlingModel> = _throttling.asStateFlow() override val throttling: MutableStateFlow<AuthenticationThrottlingModel?> = MutableStateFlow(null) private val UserRepository.selectedUserId: Int get() = getSelectedUserInfo().id Loading Loading @@ -270,10 +270,6 @@ constructor( } } override fun setThrottling(throttlingModel: AuthenticationThrottlingModel) { _throttling.value = throttlingModel } override suspend fun setThrottleDuration(durationMs: Int) { withContext(backgroundDispatcher) { lockPatternUtils.setLockoutAttemptDeadline( Loading packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt +25 −33 Original line number Diff line number Diff line Loading @@ -58,8 +58,8 @@ class AuthenticationInteractor @Inject constructor( @Application private val applicationScope: CoroutineScope, private val repository: AuthenticationRepository, @Background private val backgroundDispatcher: CoroutineDispatcher, private val repository: AuthenticationRepository, private val userRepository: UserRepository, private val clock: SystemClock, ) { Loading @@ -83,21 +83,11 @@ constructor( */ val authenticationMethod: Flow<AuthenticationMethodModel> = repository.authenticationMethod /** The current authentication throttling state, only meaningful if [isThrottled] is `true`. */ val throttling: StateFlow<AuthenticationThrottlingModel> = repository.throttling /** * Whether currently throttled and the user has to wait before being able to try another * authentication attempt. * The current authentication throttling state, set when the user has to wait before being able * to try another authentication attempt. `null` indicates throttling isn't active. */ val isThrottled: StateFlow<Boolean> = throttling .map { it.remainingMs > 0 } .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, initialValue = throttling.value.remainingMs > 0, ) val throttling: StateFlow<AuthenticationThrottlingModel?> = repository.throttling /** * Whether the auto confirm feature is enabled for the currently-selected user. Loading @@ -108,10 +98,11 @@ constructor( * During throttling, this is always disabled (`false`). */ val isAutoConfirmEnabled: StateFlow<Boolean> = combine(repository.isAutoConfirmFeatureEnabled, isThrottled) { featureEnabled, isThrottled -> combine(repository.isAutoConfirmFeatureEnabled, repository.throttling) { featureEnabled, throttling -> // Disable auto-confirm during throttling. featureEnabled && !isThrottled featureEnabled && throttling == null } .stateIn( scope = applicationScope, Loading Loading @@ -197,9 +188,8 @@ constructor( val authMethod = getAuthenticationMethod() val skipCheck = when { // We're being throttled, the UI layer should not have called this; skip the // attempt. isThrottled.value -> true // Throttling is active, the UI layer should not have called this; skip the attempt. throttling.value != null -> true // The input is too short; skip the attempt. input.isTooShort(authMethod) -> true // Auto-confirm attempt when the feature is not enabled; skip the attempt. Loading Loading @@ -259,7 +249,7 @@ constructor( cancelThrottlingCountdown() throttlingCountdownJob = applicationScope.launch { while (refreshThrottling() > 0) { while (refreshThrottling()) { delay(1.seconds.inWholeMilliseconds) } } Loading @@ -274,7 +264,7 @@ constructor( /** Notifies that the currently-selected user has changed. */ private suspend fun onSelectedUserChanged() { cancelThrottlingCountdown() if (refreshThrottling() > 0) { if (refreshThrottling()) { startThrottlingCountdown() } } Loading @@ -282,22 +272,24 @@ constructor( /** * 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. * @return Whether throttling is active or not. */ private suspend fun refreshThrottling(): Long { return withContext("$TAG#refreshThrottling", backgroundDispatcher) { private suspend fun refreshThrottling(): Boolean { withContext("$TAG#refreshThrottling", backgroundDispatcher) { val failedAttemptCount = async { repository.getFailedAuthenticationAttemptCount() } val deadline = async { repository.getThrottlingEndTimestamp() } val remainingMs = max(0, deadline.await() - clock.elapsedRealtime()) repository.setThrottling( repository.throttling.value = if (remainingMs > 0) { AuthenticationThrottlingModel( failedAttemptCount = failedAttemptCount.await(), remainingMs = remainingMs.toInt(), ), ) remainingMs } else { null // Throttling ended. } } return repository.throttling.value != null } private fun AuthenticationMethodModel.createCredential( Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt +22 −35 Original line number Diff line number Diff line Loading @@ -76,19 +76,21 @@ class AuthenticationInteractorTest : SysuiTestCase() { } @Test fun authenticate_withCorrectPin_returnsTrue() = fun authenticate_withCorrectPin_succeeds() = testScope.runTest { val isThrottled by collectLastValue(underTest.isThrottled) val throttling by collectLastValue(underTest.throttling) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) .isEqualTo(AuthenticationResult.SUCCEEDED) assertThat(isThrottled).isFalse() assertThat(throttling).isNull() } @Test fun authenticate_withIncorrectPin_returnsFalse() = fun authenticate_withIncorrectPin_fails() = testScope.runTest { utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4))) .isEqualTo(AuthenticationResult.FAILED) } Loading @@ -101,7 +103,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { } @Test fun authenticate_withCorrectMaxLengthPin_returnsTrue() = fun authenticate_withCorrectMaxLengthPin_succeeds() = testScope.runTest { val pin = List(16) { 9 } utils.authenticationRepository.apply { Loading @@ -113,10 +115,10 @@ class AuthenticationInteractorTest : SysuiTestCase() { } @Test fun authenticate_withCorrectTooLongPin_returnsFalse() = fun authenticate_withCorrectTooLongPin_fails() = testScope.runTest { // Max pin length is 16 digits. To avoid issues with overflows, this test ensures // that all pins > 16 decimal digits are rejected. // Max pin length is 16 digits. To avoid issues with overflows, this test ensures that // all pins > 16 decimal digits are rejected. // If the policy changes, there is work to do in SysUI. assertThat(DevicePolicyManager.MAX_PASSWORD_LENGTH).isLessThan(17) Loading @@ -127,20 +129,20 @@ class AuthenticationInteractorTest : SysuiTestCase() { } @Test fun authenticate_withCorrectPassword_returnsTrue() = fun authenticate_withCorrectPassword_succeeds() = testScope.runTest { val isThrottled by collectLastValue(underTest.isThrottled) val throttling by collectLastValue(underTest.throttling) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) assertThat(underTest.authenticate("password".toList())) .isEqualTo(AuthenticationResult.SUCCEEDED) assertThat(isThrottled).isFalse() assertThat(throttling).isNull() } @Test fun authenticate_withIncorrectPassword_returnsFalse() = fun authenticate_withIncorrectPassword_fails() = testScope.runTest { utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password Loading @@ -151,7 +153,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { } @Test fun authenticate_withCorrectPattern_returnsTrue() = fun authenticate_withCorrectPattern_succeeds() = testScope.runTest { utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pattern Loading @@ -162,7 +164,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { } @Test fun authenticate_withIncorrectPattern_returnsFalse() = fun authenticate_withIncorrectPattern_fails() = testScope.runTest { utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pattern Loading @@ -185,7 +187,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNull() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) val isThrottled by collectLastValue(underTest.isThrottled) val throttling by collectLastValue(underTest.throttling) utils.authenticationRepository.apply { setAuthenticationMethod(AuthenticationMethodModel.Pin) setAutoConfirmFeatureEnabled(true) Loading @@ -201,7 +203,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { ) ) .isEqualTo(AuthenticationResult.SKIPPED) assertThat(isThrottled).isFalse() assertThat(throttling).isNull() } @Test Loading Loading @@ -316,22 +318,18 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun throttling() = testScope.runTest { val throttling by collectLastValue(underTest.throttling) val isThrottled by collectLastValue(underTest.isThrottled) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN) assertThat(isThrottled).isFalse() assertThat(throttling).isEqualTo(AuthenticationThrottlingModel()) assertThat(throttling).isNull() // 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(isThrottled).isFalse() assertThat(throttling).isEqualTo(AuthenticationThrottlingModel()) assertThat(throttling).isNull() } // Make one more wrong attempt, leading to throttling: underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN assertThat(isThrottled).isTrue() assertThat(throttling) .isEqualTo( AuthenticationThrottlingModel( Loading @@ -344,7 +342,6 @@ class AuthenticationInteractorTest : SysuiTestCase() { // Correct PIN, but throttled, so doesn't attempt it: assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) .isEqualTo(AuthenticationResult.SKIPPED) assertThat(isThrottled).isTrue() assertThat(throttling) .isEqualTo( AuthenticationThrottlingModel( Loading @@ -360,7 +357,6 @@ class AuthenticationInteractorTest : SysuiTestCase() { .toInt() repeat(throttleTimeoutSec - 1) { time -> advanceTimeBy(1000) assertThat(isThrottled).isTrue() assertThat(throttling) .isEqualTo( AuthenticationThrottlingModel( Loading @@ -376,21 +372,12 @@ class AuthenticationInteractorTest : SysuiTestCase() { // Move the clock forward one more second, to completely finish the throttling period: advanceTimeBy(1000) assertThat(isThrottled).isFalse() assertThat(throttling) .isEqualTo( AuthenticationThrottlingModel( failedAttemptCount = FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING, remainingMs = 0, ) ) assertThat(throttling).isNull() // Correct PIN and no longer throttled so unlocks successfully: assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) .isEqualTo(AuthenticationResult.SUCCEEDED) assertThat(isThrottled).isFalse() assertThat(throttling).isEqualTo(AuthenticationThrottlingModel()) assertThat(throttling).isNull() } @Test Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt +3 −14 Original line number Diff line number Diff line Loading @@ -249,12 +249,10 @@ 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) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(isThrottled).isFalse() assertThat(throttling).isEqualTo(AuthenticationThrottlingModel()) assertThat(throttling).isNull() repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { times -> // Wrong PIN. assertThat(underTest.authenticate(listOf(6, 7, 8, 9))) Loading @@ -265,7 +263,6 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(message).isEqualTo(MESSAGE_WRONG_PIN) } } assertThat(isThrottled).isTrue() assertThat(throttling) .isEqualTo( AuthenticationThrottlingModel( Loading Loading @@ -300,20 +297,12 @@ class BouncerInteractorTest : SysuiTestCase() { } } assertThat(message).isEqualTo("") assertThat(isThrottled).isFalse() assertThat(throttling) .isEqualTo( AuthenticationThrottlingModel( failedAttemptCount = FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING, ) ) assertThat(throttling).isNull() // Correct PIN and no longer throttled so changes to the Gone scene: assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) .isEqualTo(AuthenticationResult.SUCCEEDED) assertThat(isThrottled).isFalse() assertThat(throttling).isEqualTo(AuthenticationThrottlingModel()) assertThat(throttling).isNull() } @Test Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt +2 −8 Original line number Diff line number Diff line Loading @@ -337,20 +337,14 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { } val remainingTimeMs = 30_000 authenticationRepository.setThrottleDuration(remainingTimeMs) authenticationRepository.setThrottling( authenticationRepository.throttling.value = AuthenticationThrottlingModel( failedAttemptCount = failedAttemptCount, remainingMs = remainingTimeMs, ) ) } else { authenticationRepository.reportAuthenticationAttempt(true) authenticationRepository.setThrottling( AuthenticationThrottlingModel( failedAttemptCount = failedAttemptCount, remainingMs = 0, ) ) authenticationRepository.throttling.value = null } runCurrent() Loading
packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt +8 −12 Original line number Diff line number Diff line Loading @@ -80,7 +80,7 @@ interface AuthenticationRepository { * The exact length a PIN should be for us to enable PIN length hinting. * * A PIN that's shorter or longer than this is not eligible for the UI to render hints showing * how many digits the current PIN is, even if [isAutoConfirmEnabled] is enabled. * how many digits the current PIN is, even if [isAutoConfirmFeatureEnabled] is enabled. * * Note that PIN length hinting is only available if the PIN auto confirmation feature is * available. Loading @@ -90,8 +90,11 @@ interface AuthenticationRepository { /** Whether the pattern should be visible for the currently-selected user. */ val isPatternVisible: StateFlow<Boolean> /** The current throttling state, as cached via [setThrottling]. */ val throttling: StateFlow<AuthenticationThrottlingModel> /** * The current authentication throttling state, set when the user has to wait before being able * to try another authentication attempt. `null` indicates throttling isn't active. */ val throttling: MutableStateFlow<AuthenticationThrottlingModel?> /** * The currently-configured authentication method. This determines how the authentication Loading Loading @@ -146,9 +149,6 @@ interface AuthenticationRepository { */ 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). Loading Loading @@ -190,8 +190,8 @@ constructor( getFreshValue = lockPatternUtils::isVisiblePatternEnabled, ) private val _throttling = MutableStateFlow(AuthenticationThrottlingModel()) override val throttling: StateFlow<AuthenticationThrottlingModel> = _throttling.asStateFlow() override val throttling: MutableStateFlow<AuthenticationThrottlingModel?> = MutableStateFlow(null) private val UserRepository.selectedUserId: Int get() = getSelectedUserInfo().id Loading Loading @@ -270,10 +270,6 @@ constructor( } } override fun setThrottling(throttlingModel: AuthenticationThrottlingModel) { _throttling.value = throttlingModel } override suspend fun setThrottleDuration(durationMs: Int) { withContext(backgroundDispatcher) { lockPatternUtils.setLockoutAttemptDeadline( Loading
packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt +25 −33 Original line number Diff line number Diff line Loading @@ -58,8 +58,8 @@ class AuthenticationInteractor @Inject constructor( @Application private val applicationScope: CoroutineScope, private val repository: AuthenticationRepository, @Background private val backgroundDispatcher: CoroutineDispatcher, private val repository: AuthenticationRepository, private val userRepository: UserRepository, private val clock: SystemClock, ) { Loading @@ -83,21 +83,11 @@ constructor( */ val authenticationMethod: Flow<AuthenticationMethodModel> = repository.authenticationMethod /** The current authentication throttling state, only meaningful if [isThrottled] is `true`. */ val throttling: StateFlow<AuthenticationThrottlingModel> = repository.throttling /** * Whether currently throttled and the user has to wait before being able to try another * authentication attempt. * The current authentication throttling state, set when the user has to wait before being able * to try another authentication attempt. `null` indicates throttling isn't active. */ val isThrottled: StateFlow<Boolean> = throttling .map { it.remainingMs > 0 } .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, initialValue = throttling.value.remainingMs > 0, ) val throttling: StateFlow<AuthenticationThrottlingModel?> = repository.throttling /** * Whether the auto confirm feature is enabled for the currently-selected user. Loading @@ -108,10 +98,11 @@ constructor( * During throttling, this is always disabled (`false`). */ val isAutoConfirmEnabled: StateFlow<Boolean> = combine(repository.isAutoConfirmFeatureEnabled, isThrottled) { featureEnabled, isThrottled -> combine(repository.isAutoConfirmFeatureEnabled, repository.throttling) { featureEnabled, throttling -> // Disable auto-confirm during throttling. featureEnabled && !isThrottled featureEnabled && throttling == null } .stateIn( scope = applicationScope, Loading Loading @@ -197,9 +188,8 @@ constructor( val authMethod = getAuthenticationMethod() val skipCheck = when { // We're being throttled, the UI layer should not have called this; skip the // attempt. isThrottled.value -> true // Throttling is active, the UI layer should not have called this; skip the attempt. throttling.value != null -> true // The input is too short; skip the attempt. input.isTooShort(authMethod) -> true // Auto-confirm attempt when the feature is not enabled; skip the attempt. Loading Loading @@ -259,7 +249,7 @@ constructor( cancelThrottlingCountdown() throttlingCountdownJob = applicationScope.launch { while (refreshThrottling() > 0) { while (refreshThrottling()) { delay(1.seconds.inWholeMilliseconds) } } Loading @@ -274,7 +264,7 @@ constructor( /** Notifies that the currently-selected user has changed. */ private suspend fun onSelectedUserChanged() { cancelThrottlingCountdown() if (refreshThrottling() > 0) { if (refreshThrottling()) { startThrottlingCountdown() } } Loading @@ -282,22 +272,24 @@ constructor( /** * 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. * @return Whether throttling is active or not. */ private suspend fun refreshThrottling(): Long { return withContext("$TAG#refreshThrottling", backgroundDispatcher) { private suspend fun refreshThrottling(): Boolean { withContext("$TAG#refreshThrottling", backgroundDispatcher) { val failedAttemptCount = async { repository.getFailedAuthenticationAttemptCount() } val deadline = async { repository.getThrottlingEndTimestamp() } val remainingMs = max(0, deadline.await() - clock.elapsedRealtime()) repository.setThrottling( repository.throttling.value = if (remainingMs > 0) { AuthenticationThrottlingModel( failedAttemptCount = failedAttemptCount.await(), remainingMs = remainingMs.toInt(), ), ) remainingMs } else { null // Throttling ended. } } return repository.throttling.value != null } private fun AuthenticationMethodModel.createCredential( Loading