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

Commit d6793291 authored by Grace Cheng's avatar Grace Cheng
Browse files

Implement face auth error try again button

Adds try again button upon face auth error during secure lock device,
and updates corresponding bouncer messages shown

Flag: android.security.secure_lock_device
Fixes: 406547038
Bug: 401645997
Test: atest DeviceEntryFaceAuthInteractorTest
Test: atest BouncerOverlayContentViewModelTest
Test: atest SecureLockDeviceBiometricAuthContentViewModelTest
Change-Id: I471bb058b83bdef0c94633a967b96be4d2492715
parent 71d51cd9
Loading
Loading
Loading
Loading
+56 −0
Original line number Diff line number Diff line
@@ -30,12 +30,16 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin
import com.android.systemui.biometrics.data.repository.facePropertyRepository
import com.android.systemui.biometrics.shared.model.BiometricModality
import com.android.systemui.biometrics.shared.model.FaceSensorInfo
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
import com.android.systemui.bouncer.shared.model.SecureLockDeviceBouncerActionButtonModel
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Overlays
@@ -263,6 +267,58 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() {
            activateViewModelJob.cancel()
        }

    @EnableFlags(FLAG_SECURE_LOCK_DEVICE)
    @Test
    fun showsTryAgainButtonOnFaceAuthFailure_hidesAfterButtonClicked_duringSecureLockDevice() =
        testScope.runTest {
            val underTest = kosmos.bouncerActionButtonInteractor
            val secureLockDeviceActionButton by
                collectLastValue(underTest.secureLockDeviceActionButton)
            val activateViewModelJob = launch {
                kosmos.secureLockDeviceBiometricAuthContentViewModel.activate()
            }
            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
            kosmos.fakeSecureLockDeviceRepository.onSecureLockDeviceEnabled()
            runCurrent()

            // Only face auth is allowed & enrolled to allow retry
            kosmos.biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(true)
            kosmos.biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
            kosmos.facePropertyRepository.setSensorInfo(
                FaceSensorInfo(id = 0, strength = SensorStrength.STRONG)
            )
            kosmos.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
            runCurrent()

            // After PIN auth
            kosmos.fakeSecureLockDeviceRepository.onSuccessfulPrimaryAuth()
            runCurrent()

            // After face auth error
            kosmos.secureLockDeviceBiometricAuthContentViewModel.showTemporaryError(
                authenticateAfterError = false,
                failedModality = BiometricModality.Face,
            )
            runCurrent()

            assertThat(
                    secureLockDeviceActionButton
                        is SecureLockDeviceBouncerActionButtonModel.TryAgainButtonModel
                )
                .isTrue()

            // After clicking try again button
            kosmos.secureLockDeviceBiometricAuthContentViewModel.onTryAgainButtonClicked()
            runCurrent()

            assertThat(
                    secureLockDeviceActionButton
                        is SecureLockDeviceBouncerActionButtonModel.TryAgainButtonModel
                )
                .isFalse()
            activateViewModelJob.cancel()
        }

    companion object {
        private const val MESSAGE_EMERGENCY_CALL = "Emergency"
        private const val MESSAGE_RETURN_TO_CALL = "Return to call"
+12 −0
Original line number Diff line number Diff line
@@ -917,6 +917,18 @@ class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() {
            assertThat(faceAuthRepository.runningAuthRequest.value).isNull()
        }

    @EnableFlags(FLAG_SECURE_LOCK_DEVICE)
    @Test
    fun faceAuthIsNotRequestedWhenPendingRetryBiometricAuth_inSecureLockDeviceMode() =
        kosmos.runTest {
            underTest.onSecureLockDeviceTryAgainButtonShowingChanged(true)
            underTest.start()
            underTest.onSwipeUpOnBouncer()

            runCurrent()
            assertThat(faceAuthRepository.runningAuthRequest.value).isNull()
        }

    @Test
    fun lockedOut_providesSameValueFromRepository() =
        kosmos.runTest {
+25 −0
Original line number Diff line number Diff line
@@ -87,6 +87,31 @@ class SecureLockDeviceBiometricAuthContentViewModelTest : SysuiTestCase() {
            assertThat(showingError[2]).isFalse()
        }

    @Test
    fun updatesStateOnRetryAfterFaceFailureOrError() =
        testScope.runTest {
            val isAuthenticating by collectLastValue(underTest.isAuthenticating)
            val isAuthenticated by collectLastValue(underTest.isAuthenticated)
            val showingError by collectLastValue(underTest.showingError)
            val canTryAgainNow by collectLastValue(underTest.canTryAgainNow)

            // On face error shown
            underTest.showTemporaryError(
                authenticateAfterError = false,
                failedModality = BiometricModality.Face,
            )
            runCurrent()

            // On retry button clicked
            underTest.onTryAgainButtonClicked()

            // Verify internal state updated to restart authentication
            assertThat(isAuthenticating).isTrue()
            assertThat(showingError).isFalse()
            assertThat(isAuthenticated?.isAuthenticated).isFalse()
            assertThat(canTryAgainNow).isFalse()
        }

    @Test
    fun updatesStateAndSkipsHaptics_onFaceHelp() =
        testScope.runTest {
+16 −2
Original line number Diff line number Diff line
@@ -111,7 +111,10 @@ constructor(
     * be shown.
     */
    val secureLockDeviceActionButton: Flow<SecureLockDeviceBouncerActionButtonModel?> =
        secureLockDeviceInteractor.showConfirmBiometricAuthButton.asUnitFlow
        merge(
                secureLockDeviceInteractor.showConfirmBiometricAuthButton.asUnitFlow,
                secureLockDeviceInteractor.showTryAgainButton.asUnitFlow,
            )
            .map {
                when {
                    isConfirmStrongBiometricAuthButton() ->
@@ -123,7 +126,14 @@ constructor(
                                    com.android.systemui.res.R.string
                                        .accessibility_confirm_biometric_auth_to_unlock,
                            )
                    else -> null
                    isTryAgainButton() ->
                        SecureLockDeviceBouncerActionButtonModel.TryAgainButtonModel(
                            labelResourceId =
                                com.android.systemui.res.R.string.biometric_dialog_try_again,
                            contentDescId =
                                com.android.systemui.res.R.string.accessibility_retry_face_auth,
                        )
                    else -> null // Do not show the button.
                }
            }
            .distinctUntilChanged()
@@ -187,6 +197,10 @@ constructor(
        return secureLockDevice() && secureLockDeviceInteractor.showConfirmBiometricAuthButton.value
    }

    private fun isTryAgainButton(): Boolean {
        return secureLockDevice() && secureLockDeviceInteractor.showTryAgainButton.value
    }

    private fun prepareToPerformAction() {
        if (SceneContainerFlag.isEnabled) {
            sceneInteractor.get().changeScene(Scenes.Lockscreen, "Bouncer action button clicked")
+5 −0
Original line number Diff line number Diff line
@@ -28,4 +28,9 @@ sealed class SecureLockDeviceBouncerActionButtonModel(
        @StringRes private val labelResourceId: Int,
        @StringRes private val contentDescId: Int,
    ) : SecureLockDeviceBouncerActionButtonModel(labelResourceId, contentDescId)

    data class TryAgainButtonModel(
        @StringRes private val labelResourceId: Int,
        @StringRes private val contentDescId: Int,
    ) : SecureLockDeviceBouncerActionButtonModel(labelResourceId, contentDescId)
}
Loading