Support class 3 face auth

Depending on the strength of the face sensor we will use either `BiometricSettingsRepository#isStrongBiometricAllowed` or `BiometricSettingsRepository#isNonStrongBiometricAllowed`

Fixes: 275788040
Test: atest DeviceEntryFaceAuthRepositoryTest
Change-Id: I1a0a4767830fc0150fdc9c17ee19bd66d30d2814
This commit is contained in:
Chandru S
2023-07-19 20:20:06 -07:00
parent 825a9c8b80
commit a15b1b7957
4 changed files with 82 additions and 30 deletions

View File

@@ -26,6 +26,8 @@ import com.android.internal.logging.UiEventLogger
import com.android.keyguard.FaceAuthUiEvent
import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.biometrics.data.repository.FacePropertyRepository
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -58,6 +60,7 @@ import java.util.stream.Collectors
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.delay
@@ -68,6 +71,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
@@ -120,6 +124,7 @@ interface DeviceEntryFaceAuthRepository {
fun cancel()
}
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class DeviceEntryFaceAuthRepositoryImpl
@Inject
@@ -143,7 +148,8 @@ constructor(
@FaceDetectTableLog private val faceDetectLog: TableLogBuffer,
@FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val featureFlags: FeatureFlags,
featureFlags: FeatureFlags,
facePropertyRepository: FacePropertyRepository,
dumpManager: DumpManager,
) : DeviceEntryFaceAuthRepository, Dumpable {
private var authCancellationSignal: CancellationSignal? = null
@@ -163,6 +169,13 @@ constructor(
override val detectionStatus: Flow<FaceDetectionStatus>
get() = _detectionStatus.filterNotNull()
private val isFaceBiometricsAllowed: Flow<Boolean> =
facePropertyRepository.sensorInfo.flatMapLatest {
if (it?.strength == SensorStrength.STRONG)
biometricSettingsRepository.isStrongBiometricAllowed
else biometricSettingsRepository.isNonStrongBiometricAllowed
}
private val _isLockedOut = MutableStateFlow(false)
override val isLockedOut: StateFlow<Boolean> = _isLockedOut
@@ -274,10 +287,8 @@ constructor(
canFaceAuthOrDetectRun(faceDetectLog),
logAndObserve(isBypassEnabled, "isBypassEnabled", faceDetectLog),
logAndObserve(
biometricSettingsRepository.isNonStrongBiometricAllowed
.isFalse()
.or(trustRepository.isCurrentUserTrusted),
"nonStrongBiometricIsNotAllowedOrCurrentUserIsTrusted",
isFaceBiometricsAllowed.isFalse().or(trustRepository.isCurrentUserTrusted),
"biometricIsNotAllowedOrCurrentUserIsTrusted",
faceDetectLog
),
// We don't want to run face detect if fingerprint can be used to unlock the device
@@ -368,21 +379,12 @@ constructor(
listOf(
canFaceAuthOrDetectRun(faceAuthLog),
logAndObserve(isLockedOut.isFalse(), "isNotInLockOutState", faceAuthLog),
logAndObserve(
deviceEntryFingerprintAuthRepository.isLockedOut.isFalse(),
"fpIsNotLockedOut",
faceAuthLog
),
logAndObserve(
trustRepository.isCurrentUserTrusted.isFalse(),
"currentUserIsNotTrusted",
faceAuthLog
),
logAndObserve(
biometricSettingsRepository.isNonStrongBiometricAllowed,
"nonStrongBiometricIsAllowed",
faceAuthLog
),
logAndObserve(isFaceBiometricsAllowed, "isFaceBiometricsAllowed", faceAuthLog),
logAndObserve(isAuthenticated.isFalse(), "faceNotAuthenticated", faceAuthLog),
)
.reduce(::and)

View File

@@ -40,6 +40,9 @@ import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FaceSensorInfo
import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.coroutines.FlowValue
@@ -151,6 +154,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
private lateinit var fakeCommandQueue: FakeCommandQueue
private lateinit var featureFlags: FakeFeatureFlags
private lateinit var fakeFacePropertyRepository: FakeFacePropertyRepository
private var wasAuthCancelled = false
private var wasDetectCancelled = false
@@ -224,6 +228,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
repository = keyguardTransitionRepository,
)
.keyguardTransitionInteractor
fakeFacePropertyRepository = FakeFacePropertyRepository()
return DeviceEntryFaceAuthRepositoryImpl(
mContext,
fmOverride,
@@ -245,6 +250,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
faceAuthBuffer,
keyguardTransitionInteractor,
featureFlags,
fakeFacePropertyRepository,
dumpManager,
)
}
@@ -590,6 +596,17 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
}
}
@Test
fun authenticateDoesNotRunWhenStrongBiometricIsNotAllowedAndFaceSensorIsStrong() =
testScope.runTest {
fakeFacePropertyRepository.setSensorInfo(FaceSensorInfo(1, SensorStrength.STRONG))
runCurrent()
testGatingCheckForFaceAuth(isFaceStrong = true) {
biometricSettingsRepository.setIsStrongBiometricAllowed(false)
}
}
@Test
fun authenticateDoesNotRunWhenSecureCameraIsActive() =
testScope.runTest {
@@ -922,6 +939,19 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
}
}
@Test
fun detectDoesNotRunWhenStrongBiometricIsAllowedAndFaceAuthSensorStrengthIsStrong() =
testScope.runTest {
fakeFacePropertyRepository.setSensorInfo(FaceSensorInfo(1, SensorStrength.STRONG))
runCurrent()
testGatingCheckForDetect(isFaceStrong = true) {
biometricSettingsRepository.setIsStrongBiometricAllowed(true)
// this shouldn't matter as face is set as a strong sensor
biometricSettingsRepository.setIsNonStrongBiometricAllowed(false)
}
}
@Test
fun detectDoesNotRunIfUdfpsIsRunning() =
testScope.runTest {
@@ -1013,9 +1043,12 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
faceAuthenticateIsCalled()
}
private suspend fun TestScope.testGatingCheckForFaceAuth(gatingCheckModifier: () -> Unit) {
private suspend fun TestScope.testGatingCheckForFaceAuth(
isFaceStrong: Boolean = false,
gatingCheckModifier: () -> Unit
) {
initCollectors()
allPreconditionsToRunFaceAuthAreTrue()
allPreconditionsToRunFaceAuthAreTrue(isFaceStrong)
gatingCheckModifier()
runCurrent()
@@ -1024,7 +1057,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
assertThat(underTest.canRunFaceAuth.value).isFalse()
// flip the gating check back on.
allPreconditionsToRunFaceAuthAreTrue()
allPreconditionsToRunFaceAuthAreTrue(isFaceStrong)
triggerFaceAuth(false)
@@ -1043,12 +1076,19 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
faceAuthenticateIsNotCalled()
}
private suspend fun TestScope.testGatingCheckForDetect(gatingCheckModifier: () -> Unit) {
private suspend fun TestScope.testGatingCheckForDetect(
isFaceStrong: Boolean = false,
gatingCheckModifier: () -> Unit
) {
initCollectors()
allPreconditionsToRunFaceAuthAreTrue()
// This will stop face auth from running but is required to be false for detect.
biometricSettingsRepository.setIsNonStrongBiometricAllowed(false)
if (isFaceStrong) {
biometricSettingsRepository.setStrongBiometricAllowed(false)
} else {
// This will stop face auth from running but is required to be false for detect.
biometricSettingsRepository.setIsNonStrongBiometricAllowed(false)
}
runCurrent()
assertThat(canFaceAuthRun()).isFalse()
@@ -1083,7 +1123,9 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
cancellationSignal.value.setOnCancelListener { wasAuthCancelled = true }
}
private suspend fun TestScope.allPreconditionsToRunFaceAuthAreTrue() {
private suspend fun TestScope.allPreconditionsToRunFaceAuthAreTrue(
isFaceStrong: Boolean = false
) {
verify(faceManager, atLeastOnce())
.addLockoutResetCallback(faceLockoutResetCallback.capture())
biometricSettingsRepository.setFaceEnrolled(true)
@@ -1098,7 +1140,11 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
WakeSleepReason.OTHER
)
)
biometricSettingsRepository.setIsNonStrongBiometricAllowed(true)
if (isFaceStrong) {
biometricSettingsRepository.setStrongBiometricAllowed(true)
} else {
biometricSettingsRepository.setIsNonStrongBiometricAllowed(true)
}
biometricSettingsRepository.setIsUserInLockdown(false)
fakeUserRepository.setSelectedUserInfo(primaryUser)
biometricSettingsRepository.setIsFaceAuthSupportedInCurrentPosture(true)

View File

@@ -342,12 +342,13 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() {
}
@Test
fun faceUnlockIsDisabledWhenFpIsLockedOut() = testScope.runTest {
underTest.start()
fun faceUnlockIsDisabledWhenFpIsLockedOut() =
testScope.runTest {
underTest.start()
fakeDeviceEntryFingerprintAuthRepository.setLockedOut(true)
runCurrent()
fakeDeviceEntryFingerprintAuthRepository.setLockedOut(true)
runCurrent()
assertThat(faceAuthRepository.wasDisabled).isTrue()
}
assertThat(faceAuthRepository.wasDisabled).isTrue()
}
}

View File

@@ -59,7 +59,6 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository {
private val _authFlags = MutableStateFlow(AuthenticationFlags(0, 0))
override val authenticationFlags: Flow<AuthenticationFlags>
get() = _authFlags
fun setFingerprintEnrolled(isFingerprintEnrolled: Boolean) {
_isFingerprintEnrolled.value = isFingerprintEnrolled
}
@@ -110,4 +109,8 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository {
fun setIsNonStrongBiometricAllowed(value: Boolean) {
_isNonStrongBiometricAllowed.value = value
}
fun setIsStrongBiometricAllowed(value: Boolean) {
_isStrongBiometricAllowed.value = value
}
}