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

Commit f2c66dee authored by Chandru S's avatar Chandru S
Browse files

Whenever face auth is locked out, provide the locked out error message in...

Whenever face auth is locked out, provide the locked out error message in response to face auth triggers

Fixes: 282093034
Test: atest KeyguardFaceAuthInteractorTest
Change-Id: Ibffbeadceb1716a7090c00ef007b961802db2024
parent 12d35805
Loading
Loading
Loading
Loading
+23 −4
Original line number Diff line number Diff line
@@ -16,9 +16,12 @@

package com.android.systemui.keyguard.domain.interactor

import android.content.Context
import android.hardware.biometrics.BiometricFaceConstants
import com.android.keyguard.FaceAuthUiEvent
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.CoreStartable
import com.android.systemui.R
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dagger.SysUISingleton
@@ -27,6 +30,8 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.shared.model.AuthenticationStatus
import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.util.kotlin.pairwise
@@ -34,7 +39,9 @@ import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
@@ -50,6 +57,7 @@ import kotlinx.coroutines.launch
class SystemUIKeyguardFaceAuthInteractor
@Inject
constructor(
    private val context: Context,
    @Application private val applicationScope: CoroutineScope,
    @Main private val mainDispatcher: CoroutineDispatcher,
    private val repository: DeviceEntryFaceAuthRepository,
@@ -157,18 +165,29 @@ constructor(
        repository.cancel()
    }

    private val _authenticationStatusOverride = MutableStateFlow<AuthenticationStatus?>(null)
    /** Provide the status of face authentication */
    override val authenticationStatus = repository.authenticationStatus
    override val authenticationStatus =
        merge(_authenticationStatusOverride.filterNotNull(), repository.authenticationStatus)

    /** Provide the status of face detection */
    override val detectionStatus = repository.detectionStatus

    private fun runFaceAuth(uiEvent: FaceAuthUiEvent, fallbackToDetect: Boolean) {
        if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) {
            if (repository.isLockedOut.value) {
                _authenticationStatusOverride.value =
                    ErrorAuthenticationStatus(
                        BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT,
                        context.resources.getString(R.string.keyguard_face_unlock_unavailable)
                    )
            } else {
                _authenticationStatusOverride.value = null
                applicationScope.launch {
                    faceAuthenticationLogger.authRequested(uiEvent)
                    repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect)
                }
            }
        } else {
            faceAuthenticationLogger.ignoredFaceAuthTrigger(
                uiEvent,
+7 −2
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.shared.model

import android.hardware.face.FaceManager
import android.os.SystemClock.elapsedRealtime

/**
 * Authentication status provided by
@@ -38,8 +39,12 @@ data class AcquiredAuthenticationStatus(val acquiredInfo: Int) : AuthenticationS
object FailedAuthenticationStatus : AuthenticationStatus()

/** Face authentication error message */
data class ErrorAuthenticationStatus(val msgId: Int, val msg: String? = null) :
    AuthenticationStatus() {
data class ErrorAuthenticationStatus(
    val msgId: Int,
    val msg: String? = null,
    // present to break equality check if the same error occurs repeatedly.
    val createdAt: Long = elapsedRealtime()
) : AuthenticationStatus() {
    /**
     * Method that checks if [msgId] is a lockout error. A lockout error means that face
     * authentication is locked out.
+3 −7
Original line number Diff line number Diff line
@@ -418,13 +418,9 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
                FACE_ERROR_CANCELED,
                "First auth attempt cancellation completed"
            )
            assertThat(authStatus())
                .isEqualTo(
                    ErrorAuthenticationStatus(
                        FACE_ERROR_CANCELED,
                        "First auth attempt cancellation completed"
                    )
                )
            val value = authStatus() as ErrorAuthenticationStatus
            assertThat(value.msgId).isEqualTo(FACE_ERROR_CANCELED)
            assertThat(value.msg).isEqualTo("First auth attempt cancellation completed")

            faceAuthenticateIsCalled()
            uiEventIsLogged(FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN)
+20 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@

package com.android.systemui.keyguard.domain.interactor

import android.hardware.biometrics.BiometricFaceConstants
import android.os.Handler
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -30,6 +31,7 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInte
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.bouncer.ui.BouncerView
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.dump.logcatLogBuffer
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
@@ -39,6 +41,7 @@ import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepo
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -90,6 +93,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() {

        underTest =
            SystemUIKeyguardFaceAuthInteractor(
                mContext,
                testScope.backgroundScope,
                dispatcher,
                faceAuthRepository,
@@ -143,6 +147,22 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() {
                )
        }

    @Test
    fun whenFaceIsLockedOutAnyAttemptsToTriggerFaceAuthMustProvideLockoutError() =
        testScope.runTest {
            underTest.start()
            val authenticationStatus = collectLastValue(underTest.authenticationStatus)
            faceAuthRepository.setLockedOut(true)

            underTest.onDeviceLifted()

            val outputValue = authenticationStatus()!! as ErrorAuthenticationStatus
            assertThat(outputValue.msgId)
                .isEqualTo(BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT)
            assertThat(outputValue.msg).isEqualTo("Face Unlock unavailable")
            assertThat(faceAuthRepository.runningAuthRequest.value).isNull()
        }

    @Test
    fun faceAuthIsRequestedWhenLockscreenBecomesVisibleFromAodState() =
        testScope.runTest {
+7 −1
Original line number Diff line number Diff line
@@ -41,7 +41,9 @@ class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository {
    fun setDetectionStatus(status: DetectionStatus) {
        _detectionStatus.value = status
    }
    override val isLockedOut = MutableStateFlow(false)

    private val _isLockedOut = MutableStateFlow(false)
    override val isLockedOut = _isLockedOut
    private val _runningAuthRequest = MutableStateFlow<Pair<FaceAuthUiEvent, Boolean>?>(null)
    val runningAuthRequest: StateFlow<Pair<FaceAuthUiEvent, Boolean>?> =
        _runningAuthRequest.asStateFlow()
@@ -56,6 +58,10 @@ class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository {
        _isAuthRunning.value = true
    }

    fun setLockedOut(value: Boolean) {
        _isLockedOut.value = value
    }

    override fun cancel() {
        _isAuthRunning.value = false
        _runningAuthRequest.value = null