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

Commit 713db296 authored by Austin Delgado's avatar Austin Delgado
Browse files

Fix BP implicit flow failure

Require confirmation after implicit auth failure by falling back to explicit flow.

Test: PromptViewModelTest
Bug: 296521248
Change-Id: I3e8da6dd2ee196e2dc8551c066a1ed0155f9cf4f
parent dc2ea290
Loading
Loading
Loading
Loading
+10 −17
Original line number Diff line number Diff line
@@ -78,17 +78,6 @@ constructor(

    private val _isOverlayTouched: MutableStateFlow<Boolean> = MutableStateFlow(false)

    /**
     * If the API caller or the user's personal preferences require explicit confirmation after
     * successful authentication.
     */
    val isConfirmationRequired: Flow<Boolean> =
        combine(_isOverlayTouched, promptSelectorInteractor.isConfirmationRequired) {
            isOverlayTouched,
            isConfirmationRequired ->
            !isOverlayTouched && isConfirmationRequired
        }

    /** The kind of credential the user has. */
    val credentialKind: Flow<PromptKind> = promptSelectorInteractor.credentialKind

@@ -137,6 +126,15 @@ constructor(
            }
            .distinctUntilChanged()

    /**
     * If the API caller or the user's personal preferences require explicit confirmation after
     * successful authentication. Confirmation always required when in explicit flow.
     */
    val isConfirmationRequired: Flow<Boolean> =
        combine(_isOverlayTouched, size) { isOverlayTouched, size ->
            !isOverlayTouched && size.isNotSmall
        }

    /** Title for the prompt. */
    val title: Flow<String> =
        promptSelectorInteractor.prompt.map { it?.title ?: "" }.distinctUntilChanged()
@@ -170,12 +168,7 @@ constructor(
            .distinctUntilChanged()

    /** If the icon can be used as a confirmation button. */
    val isIconConfirmButton: Flow<Boolean> =
        combine(size, promptSelectorInteractor.isConfirmationRequired) {
            size,
            isConfirmationRequired ->
            size.isNotSmall && isConfirmationRequired
        }
    val isIconConfirmButton: Flow<Boolean> = size.map { it.isNotSmall }.distinctUntilChanged()

    /** If the negative button should be shown. */
    val isNegativeButtonVisible: Flow<Boolean> =
+80 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.hardware.biometrics.PromptInfo
import android.hardware.face.FaceSensorPropertiesInternal
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
@@ -499,6 +500,81 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
        assertThat(canTryAgain).isFalse()
    }

    @Test
    fun auto_confirm_authentication_when_finger_down() = runGenericTest {
        val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)

        // No icon button when face only, can't confirm before auth
        if (!testCase.isFaceOnly) {
            viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_DOWN))
        }
        viewModel.showAuthenticated(testCase.authenticatedModality, 0)

        val authenticating by collectLastValue(viewModel.isAuthenticating)
        val authenticated by collectLastValue(viewModel.isAuthenticated)
        val message by collectLastValue(viewModel.message)
        val size by collectLastValue(viewModel.size)
        val legacyState by collectLastValue(viewModel.legacyState)
        val canTryAgain by collectLastValue(viewModel.canTryAgainNow)

        assertThat(authenticating).isFalse()
        assertThat(canTryAgain).isFalse()
        assertThat(authenticated?.isAuthenticated).isTrue()

        if (testCase.isFaceOnly && expectConfirmation) {
            assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_PENDING_CONFIRMATION)

            assertThat(size).isEqualTo(PromptSize.MEDIUM)
            assertButtonsVisible(
                cancel = true,
                confirm = true,
            )

            viewModel.confirmAuthenticated()
            assertThat(message).isEqualTo(PromptMessage.Empty)
            assertButtonsVisible()
        } else {
            assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATED)
        }
    }

    @Test
    fun cannot_auto_confirm_authentication_when_finger_up() = runGenericTest {
        val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)

        // No icon button when face only, can't confirm before auth
        if (!testCase.isFaceOnly) {
            viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_DOWN))
            viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_UP))
        }
        viewModel.showAuthenticated(testCase.authenticatedModality, 0)

        val authenticating by collectLastValue(viewModel.isAuthenticating)
        val authenticated by collectLastValue(viewModel.isAuthenticated)
        val message by collectLastValue(viewModel.message)
        val size by collectLastValue(viewModel.size)
        val legacyState by collectLastValue(viewModel.legacyState)
        val canTryAgain by collectLastValue(viewModel.canTryAgainNow)

        assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation)
        if (expectConfirmation) {
            assertThat(size).isEqualTo(PromptSize.MEDIUM)
            assertButtonsVisible(
                cancel = true,
                confirm = true,
            )

            viewModel.confirmAuthenticated()
            assertThat(message).isEqualTo(PromptMessage.Empty)
            assertButtonsVisible()
        }

        assertThat(authenticating).isFalse()
        assertThat(authenticated?.isAuthenticated).isTrue()
        assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATED)
        assertThat(canTryAgain).isFalse()
    }

    @Test
    fun cannot_confirm_unless_authenticated() = runGenericTest {
        val authenticating by collectLastValue(viewModel.isAuthenticating)
@@ -679,6 +755,10 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
        testScope.runTest { block() }
    }

    /** Obtain a MotionEvent with the specified MotionEvent action constant */
    private fun obtainMotionEvent(action: Int): MotionEvent =
        MotionEvent.obtain(0, 0, action, 0f, 0f, 0)

    companion object {
        @JvmStatic
        @Parameterized.Parameters(name = "{0}")