Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 28861e41 authored by Danny Burakov's avatar Danny Burakov Committed by Android (Google) Code Review
Browse files

Merge "[flexiglass] Remove the redundant `isThrottled` state." into main

parents f93a7ee9 f7fea09a
Loading
Loading
Loading
Loading
+22 −35
Original line number Diff line number Diff line
@@ -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)
        }
@@ -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 {
@@ -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)
@@ -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
@@ -151,7 +153,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
        }

    @Test
    fun authenticate_withCorrectPattern_returnsTrue() =
    fun authenticate_withCorrectPattern_succeeds() =
        testScope.runTest {
            utils.authenticationRepository.setAuthenticationMethod(
                AuthenticationMethodModel.Pattern
@@ -162,7 +164,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
        }

    @Test
    fun authenticate_withIncorrectPattern_returnsFalse() =
    fun authenticate_withIncorrectPattern_fails() =
        testScope.runTest {
            utils.authenticationRepository.setAuthenticationMethod(
                AuthenticationMethodModel.Pattern
@@ -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)
@@ -201,7 +203,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
                    )
                )
                .isEqualTo(AuthenticationResult.SKIPPED)
            assertThat(isThrottled).isFalse()
            assertThat(throttling).isNull()
        }

    @Test
@@ -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(
@@ -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(
@@ -360,7 +357,6 @@ class AuthenticationInteractorTest : SysuiTestCase() {
                    .toInt()
            repeat(throttleTimeoutSec - 1) { time ->
                advanceTimeBy(1000)
                assertThat(isThrottled).isTrue()
                assertThat(throttling)
                    .isEqualTo(
                        AuthenticationThrottlingModel(
@@ -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
+3 −14
Original line number Diff line number Diff line
@@ -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)))
@@ -265,7 +263,6 @@ class BouncerInteractorTest : SysuiTestCase() {
                    assertThat(message).isEqualTo(MESSAGE_WRONG_PIN)
                }
            }
            assertThat(isThrottled).isTrue()
            assertThat(throttling)
                .isEqualTo(
                    AuthenticationThrottlingModel(
@@ -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
+2 −8
Original line number Diff line number Diff line
@@ -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()
+8 −12
Original line number Diff line number Diff line
@@ -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.
@@ -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
@@ -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).
@@ -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
@@ -270,10 +270,6 @@ constructor(
        }
    }

    override fun setThrottling(throttlingModel: AuthenticationThrottlingModel) {
        _throttling.value = throttlingModel
    }

    override suspend fun setThrottleDuration(durationMs: Int) {
        withContext(backgroundDispatcher) {
            lockPatternUtils.setLockoutAttemptDeadline(
+25 −33
Original line number Diff line number Diff line
@@ -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,
) {
@@ -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.
@@ -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,
@@ -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.
@@ -259,7 +249,7 @@ constructor(
        cancelThrottlingCountdown()
        throttlingCountdownJob =
            applicationScope.launch {
                while (refreshThrottling() > 0) {
                while (refreshThrottling()) {
                    delay(1.seconds.inWholeMilliseconds)
                }
            }
@@ -274,7 +264,7 @@ constructor(
    /** Notifies that the currently-selected user has changed. */
    private suspend fun onSelectedUserChanged() {
        cancelThrottlingCountdown()
        if (refreshThrottling() > 0) {
        if (refreshThrottling()) {
            startThrottlingCountdown()
        }
    }
@@ -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