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
This commit is contained in:
Chandru S
2023-06-25 02:02:53 +08:00
parent 12d35805d3
commit f2c66dee85
5 changed files with 60 additions and 14 deletions

View File

@@ -16,9 +16,12 @@
package com.android.systemui.keyguard.domain.interactor 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.FaceAuthUiEvent
import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.CoreStartable 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.AlternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dagger.SysUISingleton 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.FeatureFlags
import com.android.systemui.flags.Flags import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository 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.keyguard.shared.model.TransitionState
import com.android.systemui.log.FaceAuthenticationLogger import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.util.kotlin.pairwise import com.android.systemui.util.kotlin.pairwise
@@ -34,7 +39,9 @@ import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
@@ -50,6 +57,7 @@ import kotlinx.coroutines.launch
class SystemUIKeyguardFaceAuthInteractor class SystemUIKeyguardFaceAuthInteractor
@Inject @Inject
constructor( constructor(
private val context: Context,
@Application private val applicationScope: CoroutineScope, @Application private val applicationScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher, @Main private val mainDispatcher: CoroutineDispatcher,
private val repository: DeviceEntryFaceAuthRepository, private val repository: DeviceEntryFaceAuthRepository,
@@ -157,17 +165,28 @@ constructor(
repository.cancel() repository.cancel()
} }
private val _authenticationStatusOverride = MutableStateFlow<AuthenticationStatus?>(null)
/** Provide the status of face authentication */ /** 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 */ /** Provide the status of face detection */
override val detectionStatus = repository.detectionStatus override val detectionStatus = repository.detectionStatus
private fun runFaceAuth(uiEvent: FaceAuthUiEvent, fallbackToDetect: Boolean) { private fun runFaceAuth(uiEvent: FaceAuthUiEvent, fallbackToDetect: Boolean) {
if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) { if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) {
applicationScope.launch { if (repository.isLockedOut.value) {
faceAuthenticationLogger.authRequested(uiEvent) _authenticationStatusOverride.value =
repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect) 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 { } else {
faceAuthenticationLogger.ignoredFaceAuthTrigger( faceAuthenticationLogger.ignoredFaceAuthTrigger(

View File

@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.shared.model package com.android.systemui.keyguard.shared.model
import android.hardware.face.FaceManager import android.hardware.face.FaceManager
import android.os.SystemClock.elapsedRealtime
/** /**
* Authentication status provided by * Authentication status provided by
@@ -38,8 +39,12 @@ data class AcquiredAuthenticationStatus(val acquiredInfo: Int) : AuthenticationS
object FailedAuthenticationStatus : AuthenticationStatus() object FailedAuthenticationStatus : AuthenticationStatus()
/** Face authentication error message */ /** Face authentication error message */
data class ErrorAuthenticationStatus(val msgId: Int, val msg: String? = null) : data class ErrorAuthenticationStatus(
AuthenticationStatus() { 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 * Method that checks if [msgId] is a lockout error. A lockout error means that face
* authentication is locked out. * authentication is locked out.

View File

@@ -418,13 +418,9 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
FACE_ERROR_CANCELED, FACE_ERROR_CANCELED,
"First auth attempt cancellation completed" "First auth attempt cancellation completed"
) )
assertThat(authStatus()) val value = authStatus() as ErrorAuthenticationStatus
.isEqualTo( assertThat(value.msgId).isEqualTo(FACE_ERROR_CANCELED)
ErrorAuthenticationStatus( assertThat(value.msg).isEqualTo("First auth attempt cancellation completed")
FACE_ERROR_CANCELED,
"First auth attempt cancellation completed"
)
)
faceAuthenticateIsCalled() faceAuthenticateIsCalled()
uiEventIsLogged(FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN) uiEventIsLogged(FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN)

View File

@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.interactor package com.android.systemui.keyguard.domain.interactor
import android.hardware.biometrics.BiometricFaceConstants
import android.os.Handler import android.os.Handler
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest 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.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.bouncer.ui.BouncerView import com.android.systemui.bouncer.ui.BouncerView
import com.android.systemui.classifier.FalsingCollector import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.dump.logcatLogBuffer
import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags 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.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.FakeTrustRepository 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.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -90,6 +93,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() {
underTest = underTest =
SystemUIKeyguardFaceAuthInteractor( SystemUIKeyguardFaceAuthInteractor(
mContext,
testScope.backgroundScope, testScope.backgroundScope,
dispatcher, dispatcher,
faceAuthRepository, 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 @Test
fun faceAuthIsRequestedWhenLockscreenBecomesVisibleFromAodState() = fun faceAuthIsRequestedWhenLockscreenBecomesVisibleFromAodState() =
testScope.runTest { testScope.runTest {

View File

@@ -41,7 +41,9 @@ class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository {
fun setDetectionStatus(status: DetectionStatus) { fun setDetectionStatus(status: DetectionStatus) {
_detectionStatus.value = status _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) private val _runningAuthRequest = MutableStateFlow<Pair<FaceAuthUiEvent, Boolean>?>(null)
val runningAuthRequest: StateFlow<Pair<FaceAuthUiEvent, Boolean>?> = val runningAuthRequest: StateFlow<Pair<FaceAuthUiEvent, Boolean>?> =
_runningAuthRequest.asStateFlow() _runningAuthRequest.asStateFlow()
@@ -56,6 +58,10 @@ class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository {
_isAuthRunning.value = true _isAuthRunning.value = true
} }
fun setLockedOut(value: Boolean) {
_isLockedOut.value = value
}
override fun cancel() { override fun cancel() {
_isAuthRunning.value = false _isAuthRunning.value = false
_runningAuthRequest.value = null _runningAuthRequest.value = null