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

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

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
parent 825a9c8b
Loading
Loading
Loading
Loading
+17 −15
Original line number Diff line number Diff line
@@ -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)
+54 −8
Original line number Diff line number Diff line
@@ -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()

        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
            )
        )
        if (isFaceStrong) {
            biometricSettingsRepository.setStrongBiometricAllowed(true)
        } else {
            biometricSettingsRepository.setIsNonStrongBiometricAllowed(true)
        }
        biometricSettingsRepository.setIsUserInLockdown(false)
        fakeUserRepository.setSelectedUserInfo(primaryUser)
        biometricSettingsRepository.setIsFaceAuthSupportedInCurrentPosture(true)
+7 −6
Original line number Diff line number Diff line
@@ -342,7 +342,8 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() {
        }

    @Test
    fun faceUnlockIsDisabledWhenFpIsLockedOut() = testScope.runTest {
    fun faceUnlockIsDisabledWhenFpIsLockedOut() =
        testScope.runTest {
            underTest.start()

            fakeDeviceEntryFingerprintAuthRepository.setLockedOut(true)
+4 −1
Original line number Diff line number Diff line
@@ -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
    }
}