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

Commit 821c4280 authored by Chandru S's avatar Chandru S
Browse files

Provide better API methods that can provide face/fp auth status

 1. `isFingerprintAuthUsuallyAllowed` is true when fp is enrolled and is not disabled through device policy
 2. `isFingerprintAuthCurrentlyAllowed` is true when `isFingerprintAuthUsuallyAllowed` is true and strong auth flags don't restrict fp auth
 3. `isFaceAuthUsuallyAllowed` is true when face is enrolled, not disabled by device policy and enabled in biometricManager
 4. `isFaceAuthCurrentlyAllowed` is true when `isFaceAuthUsuallyAllowed` is true and face auth is allowed in current posture and strong auth flags don't prevent face auth from running
    a. If face auth is class3 then this will rely on strong biometric allowed strong auth flag
    b. If face auth is not class3 then this will rely on non strong biometric allowed strong auth flag

Motivation for the change:
 1. Modern arch replacement for methods like KeyguardUpdateMonitor `isUnlockingWithFingerprintAllowed` and `isUnlockingWithBiometricAllowed`
 2. `KeyguardUpdateMonitor#isUnlockingWithBiometricAllowed` doesn't actually check for enrollment state, which is the root cause of the bug.
 3. In all places where we check if fingerprint is enrolled, we always check if fp is enabled and if fp is allowed by strong auth flags. Same for face as well.

Bug: 293472698
Test: atest BouncerMessageRepositoryTest
Test: atest AlternateBouncerInteractorTest
Test: atest BiometricSettingsRepositoryTest
Test: atest DeviceEntryFaceAuthRepositoryTest
Change-Id: I9ae5e54aa473ff6c02d551607a3c0907585aa6a6
parent c25d75a1
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -36,7 +36,6 @@ import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART_FOR_MAINL
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R.string.bouncer_face_not_recognized
import com.android.systemui.R.string.keyguard_enter_password
import com.android.systemui.R.string.keyguard_enter_pattern
@@ -80,13 +79,14 @@ import com.android.systemui.R.string.kg_wrong_pin_try_again
import com.android.systemui.bouncer.shared.model.BouncerMessageModel
import com.android.systemui.bouncer.shared.model.Message
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import javax.inject.Inject

@SysUISingleton
class BouncerMessageFactory
@Inject
constructor(
    private val updateMonitor: KeyguardUpdateMonitor,
    private val biometricSettingsRepository: BiometricSettingsRepository,
    private val securityModel: KeyguardSecurityModel,
) {

@@ -99,7 +99,7 @@ constructor(
            getBouncerMessage(
                reason,
                securityModel.getSecurityMode(userId),
                updateMonitor.isUnlockingWithFingerprintAllowed
                biometricSettingsRepository.isFingerprintAuthCurrentlyAllowed.value
            )
        return pair?.let {
            BouncerMessageModel(
+4 −16
Original line number Diff line number Diff line
@@ -123,20 +123,11 @@ constructor(
    fingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
) : BouncerMessageRepository {

    private val isFaceEnrolledAndEnabled =
        and(
            biometricSettingsRepository.isFaceAuthenticationEnabled,
            biometricSettingsRepository.isFaceEnrolled
        )

    private val isFingerprintEnrolledAndEnabled =
        and(
            biometricSettingsRepository.isFingerprintEnabledByDevicePolicy,
            biometricSettingsRepository.isFingerprintEnrolled
        )

    private val isAnyBiometricsEnabledAndEnrolled =
        or(isFaceEnrolledAndEnabled, isFingerprintEnrolledAndEnabled)
        or(
            biometricSettingsRepository.isFaceAuthEnrolledAndEnabled,
            biometricSettingsRepository.isFingerprintEnrolledAndEnabled,
        )

    private val wasRebootedForMainlineUpdate
        get() = systemPropertiesHelper.get(SYS_BOOT_REASON_PROP) == REBOOT_MAINLINE_UPDATE
@@ -335,8 +326,5 @@ constructor(
    }
}

private fun and(flow: Flow<Boolean>, anotherFlow: Flow<Boolean>) =
    flow.combine(anotherFlow) { a, b -> a && b }

private fun or(flow: Flow<Boolean>, anotherFlow: Flow<Boolean>) =
    flow.combine(anotherFlow) { a, b -> a || b }
+1 −3
Original line number Diff line number Diff line
@@ -83,9 +83,7 @@ constructor(

    fun canShowAlternateBouncerForFingerprint(): Boolean {
        return bouncerRepository.alternateBouncerUIAvailable.value &&
            biometricSettingsRepository.isFingerprintEnrolled.value &&
            biometricSettingsRepository.isStrongBiometricAllowed.value &&
            biometricSettingsRepository.isFingerprintEnabledByDevicePolicy.value &&
            biometricSettingsRepository.isFingerprintAuthCurrentlyAllowed.value &&
            !keyguardUpdateMonitor.isFingerprintLockedOut &&
            !keyguardStateController.isUnlocked &&
            !statusBarStateController.isDozing
+68 −33
Original line number Diff line number Diff line
@@ -28,6 +28,9 @@ import com.android.internal.widget.LockPatternUtils
import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.data.repository.FacePropertyRepository
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -68,34 +71,32 @@ import kotlinx.coroutines.flow.transformLatest
 * upstream changes.
 */
interface BiometricSettingsRepository {
    /** Whether any fingerprints are enrolled for the current user. */
    val isFingerprintEnrolled: StateFlow<Boolean>

    /** Whether face authentication is enrolled for the current user. */
    val isFaceEnrolled: Flow<Boolean>

    /**
     * Whether face authentication is enabled/disabled based on system settings like device policy,
     * biometrics setting.
     * If the current user can enter the device using fingerprint. This is true if user has enrolled
     * fingerprints and fingerprint auth is not disabled through settings/device policy
     */
    val isFaceAuthenticationEnabled: Flow<Boolean>
    val isFingerprintEnrolledAndEnabled: StateFlow<Boolean>

    /**
     * Whether the current user is allowed to use a strong biometric for device entry based on
     * Android Security policies. If false, the user may be able to use primary authentication for
     * device entry.
     * If the current user can enter the device using fingerprint, right now.
     *
     * This returns true if there are no strong auth flags that restrict the user from using
     * fingerprint and [isFingerprintEnrolledAndEnabled] is true
     */
    val isStrongBiometricAllowed: StateFlow<Boolean>
    val isFingerprintAuthCurrentlyAllowed: StateFlow<Boolean>

    /**
     * Whether the current user is allowed to use a convenience biometric for device entry based on
     * Android Security policies. If false, the user may be able to use strong biometric or primary
     * authentication for device entry.
     * If the current user can use face auth to enter the device. This is true when the user has
     * face auth enrolled, and is enabled in settings/device policy.
     */
    val isNonStrongBiometricAllowed: StateFlow<Boolean>
    val isFaceAuthEnrolledAndEnabled: Flow<Boolean>

    /** Whether fingerprint feature is enabled for the current user by the DevicePolicy */
    val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean>
    /**
     * If the current user can use face auth to enter the device right now. This is true when
     * [isFaceAuthEnrolledAndEnabled] is true and strong auth settings allow face auth to run and
     * face auth is supported by the current device posture.
     */
    val isFaceAuthCurrentlyAllowed: Flow<Boolean>

    /**
     * Whether face authentication is supported for the current device posture. Face auth can be
@@ -130,6 +131,8 @@ constructor(
    @Background backgroundDispatcher: CoroutineDispatcher,
    biometricManager: BiometricManager?,
    devicePostureRepository: DevicePostureRepository,
    facePropertyRepository: FacePropertyRepository,
    fingerprintPropertyRepository: FingerprintPropertyRepository,
    dumpManager: DumpManager,
) : BiometricSettingsRepository, Dumpable {

@@ -165,7 +168,9 @@ constructor(
    }

    override fun dump(pw: PrintWriter, args: Array<String?>) {
        pw.println("isFingerprintEnrolled=${isFingerprintEnrolled.value}")
        pw.println("isFingerprintEnrolledAndEnabled=${isFingerprintEnrolledAndEnabled.value}")
        pw.println("isFingerprintAuthCurrentlyAllowed=${isFingerprintAuthCurrentlyAllowed.value}")
        pw.println("isNonStrongBiometricAllowed=${isNonStrongBiometricAllowed.value}")
        pw.println("isStrongBiometricAllowed=${isStrongBiometricAllowed.value}")
        pw.println("isFingerprintEnabledByDevicePolicy=${isFingerprintEnabledByDevicePolicy.value}")
    }
@@ -180,7 +185,7 @@ constructor(
            user = UserHandle.ALL
        )

    override val isFingerprintEnrolled: StateFlow<Boolean> =
    private val isFingerprintEnrolled: Flow<Boolean> =
        selectedUserId
            .flatMapLatest { currentUserId ->
                conflatedCallbackFlow {
@@ -211,7 +216,7 @@ constructor(
                    authController.isFingerprintEnrolled(userRepository.getSelectedUserInfo().id)
            )

    override val isFaceEnrolled: Flow<Boolean> =
    private val isFaceEnrolled: Flow<Boolean> =
        selectedUserId.flatMapLatest { selectedUserId: Int ->
            conflatedCallbackFlow {
                val callback =
@@ -245,14 +250,6 @@ constructor(
            isFaceEnabledByBiometricsManager.map { biometricsEnabledForUser[userInfo.id] ?: false }
        }

    override val isFaceAuthenticationEnabled: Flow<Boolean>
        get() =
            combine(isFaceEnabledByBiometricsManagerForCurrentUser, isFaceEnabledByDevicePolicy) {
                biometricsManagerSetting,
                devicePolicySetting ->
                biometricsManagerSetting && devicePolicySetting
            }

    private val isFaceEnabledByDevicePolicy: Flow<Boolean> =
        combine(selectedUserId, devicePolicyChangedForAllUsers) { userId, _ ->
                devicePolicyManager.isFaceDisabled(userId)
@@ -263,6 +260,13 @@ constructor(
            .flowOn(backgroundDispatcher)
            .distinctUntilChanged()

    private val isFaceAuthenticationEnabled: Flow<Boolean> =
        combine(isFaceEnabledByBiometricsManagerForCurrentUser, isFaceEnabledByDevicePolicy) {
            biometricsManagerSetting,
            devicePolicySetting ->
            biometricsManagerSetting && devicePolicySetting
        }

    private val isFaceEnabledByBiometricsManager: Flow<Pair<Int, Boolean>> =
        conflatedCallbackFlow {
                val callback =
@@ -283,7 +287,7 @@ constructor(
            // being registered.
            .stateIn(scope, SharingStarted.Eagerly, Pair(0, false))

    override val isStrongBiometricAllowed: StateFlow<Boolean> =
    private val isStrongBiometricAllowed: StateFlow<Boolean> =
        strongAuthTracker.isStrongBiometricAllowed.stateIn(
            scope,
            SharingStarted.Eagerly,
@@ -293,7 +297,7 @@ constructor(
            )
        )

    override val isNonStrongBiometricAllowed: StateFlow<Boolean> =
    private val isNonStrongBiometricAllowed: StateFlow<Boolean> =
        strongAuthTracker.isNonStrongBiometricAllowed.stateIn(
            scope,
            SharingStarted.Eagerly,
@@ -303,7 +307,19 @@ constructor(
            )
        )

    override val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean> =
    private val isFingerprintBiometricAllowed: Flow<Boolean> =
        fingerprintPropertyRepository.strength.flatMapLatest {
            if (it == SensorStrength.STRONG) isStrongBiometricAllowed
            else isNonStrongBiometricAllowed
        }

    private val isFaceBiometricsAllowed: Flow<Boolean> =
        facePropertyRepository.sensorInfo.flatMapLatest {
            if (it?.strength == SensorStrength.STRONG) isStrongBiometricAllowed
            else isNonStrongBiometricAllowed
        }

    private val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean> =
        selectedUserId
            .flatMapLatest { userId ->
                devicePolicyChangedForAllUsers
@@ -319,6 +335,25 @@ constructor(
                        userRepository.getSelectedUserInfo().id
                    )
            )

    override val isFingerprintEnrolledAndEnabled: StateFlow<Boolean> =
        isFingerprintEnrolled
            .and(isFingerprintEnabledByDevicePolicy)
            .stateIn(scope, SharingStarted.Eagerly, false)

    override val isFingerprintAuthCurrentlyAllowed: StateFlow<Boolean> =
        isFingerprintEnrolledAndEnabled
            .and(isFingerprintBiometricAllowed)
            .stateIn(scope, SharingStarted.Eagerly, false)

    override val isFaceAuthEnrolledAndEnabled: Flow<Boolean>
        get() = isFaceAuthenticationEnabled.and(isFaceEnrolled)

    override val isFaceAuthCurrentlyAllowed: Flow<Boolean>
        get() =
            isFaceAuthEnrolledAndEnabled
                .and(isFaceBiometricsAllowed)
                .and(isFaceAuthSupportedInCurrentPosture)
}

@OptIn(ExperimentalCoroutinesApi::class)
+11 −21
Original line number Diff line number Diff line
@@ -26,8 +26,6 @@ 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
@@ -72,7 +70,6 @@ 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
@@ -162,7 +159,6 @@ constructor(
    @FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
    private val featureFlags: FeatureFlags,
    facePropertyRepository: FacePropertyRepository,
    dumpManager: DumpManager,
) : DeviceEntryFaceAuthRepository, Dumpable {
    private var authCancellationSignal: CancellationSignal? = null
@@ -182,13 +178,6 @@ 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

@@ -313,8 +302,10 @@ constructor(
                canFaceAuthOrDetectRun(faceDetectLog),
                logAndObserve(isBypassEnabled, "isBypassEnabled", faceDetectLog),
                logAndObserve(
                    isFaceBiometricsAllowed.isFalse().or(trustRepository.isCurrentUserTrusted),
                    "biometricIsNotAllowedOrCurrentUserIsTrusted",
                    biometricSettingsRepository.isFaceAuthCurrentlyAllowed
                        .isFalse()
                        .or(trustRepository.isCurrentUserTrusted),
                    "faceAuthIsNotCurrentlyAllowedOrCurrentUserIsTrusted",
                    faceDetectLog
                ),
                // We don't want to run face detect if fingerprint can be used to unlock the device
@@ -346,13 +337,8 @@ constructor(
    private fun canFaceAuthOrDetectRun(tableLogBuffer: TableLogBuffer): Flow<Boolean> {
        return listOf(
                logAndObserve(
                    biometricSettingsRepository.isFaceEnrolled,
                    "isFaceEnrolled",
                    tableLogBuffer
                ),
                logAndObserve(
                    biometricSettingsRepository.isFaceAuthenticationEnabled,
                    "isFaceAuthenticationEnabled",
                    biometricSettingsRepository.isFaceAuthEnrolledAndEnabled,
                    "isFaceAuthEnrolledAndEnabled",
                    tableLogBuffer
                ),
                logAndObserve(faceAuthPaused.isFalse(), "faceAuthIsNotPaused", tableLogBuffer),
@@ -406,7 +392,11 @@ constructor(
                    "currentUserIsNotTrusted",
                    faceAuthLog
                ),
                logAndObserve(isFaceBiometricsAllowed, "isFaceBiometricsAllowed", faceAuthLog),
                logAndObserve(
                    biometricSettingsRepository.isFaceAuthCurrentlyAllowed,
                    "isFaceAuthCurrentlyAllowed",
                    faceAuthLog
                ),
                logAndObserve(isAuthenticated.isFalse(), "faceNotAuthenticated", faceAuthLog),
            )
            .reduce(::and)
Loading