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:
@@ -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,17 +165,28 @@ 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)) {
|
||||
applicationScope.launch {
|
||||
faceAuthenticationLogger.authRequested(uiEvent)
|
||||
repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect)
|
||||
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(
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user