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
|
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(
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user