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

Commit 929f4b7f authored by Chandru S's avatar Chandru S
Browse files

Use a single combine function to combine all boolean flows instead of nested combines

Reduces the number of yield points in the repository, otherwise there could some delay between one of the individual flows changing to true and the combined flow from changing to true.

Bug: 291161749
Test: atest DeviceEntryFaceAuthRepositoryTest
Change-Id: I50c5ddc32b595374741a701afec522b70440f38a
parent 67624f1b
Loading
Loading
Loading
Loading
+102 −115
Original line number Diff line number Diff line
@@ -50,7 +50,6 @@ import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.log.SessionTracker
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.UserRepository
@@ -66,9 +65,10 @@ import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOf
@@ -77,6 +77,7 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

@@ -153,7 +154,7 @@ constructor(
    private val faceAuthLogger: FaceAuthenticationLogger,
    private val biometricSettingsRepository: BiometricSettingsRepository,
    private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
    private val trustRepository: TrustRepository,
    trustRepository: TrustRepository,
    private val keyguardRepository: KeyguardRepository,
    private val keyguardInteractor: KeyguardInteractor,
    private val alternateBouncerInteractor: AlternateBouncerInteractor,
@@ -202,11 +203,9 @@ constructor(
    private val keyguardSessionId: InstanceId?
        get() = sessionTracker.getSessionId(StatusBarManager.SESSION_KEYGUARD)

    private val _canRunFaceAuth = MutableStateFlow(false)
    override val canRunFaceAuth: StateFlow<Boolean>
        get() = _canRunFaceAuth

    private val canRunDetection = MutableStateFlow(false)
    private val canRunDetection: StateFlow<Boolean>

    private val _isAuthenticated = MutableStateFlow(false)
    override val isAuthenticated: Flow<Boolean>
@@ -252,10 +251,58 @@ constructor(
        dumpManager.registerCriticalDumpable("DeviceEntryFaceAuthRepositoryImpl", this)

        if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) {
            canRunFaceAuth =
                listOf(
                        *gatingConditionsForAuthAndDetect(),
                        Pair(isLockedOut.isFalse(), "isNotInLockOutState"),
                        Pair(
                            trustRepository.isCurrentUserTrusted.isFalse(),
                            "currentUserIsNotTrusted"
                        ),
                        Pair(
                            biometricSettingsRepository.isFaceAuthCurrentlyAllowed,
                            "isFaceAuthCurrentlyAllowed"
                        ),
                        Pair(isAuthenticated.isFalse(), "faceNotAuthenticated"),
                    )
                    .andAllFlows("canFaceAuthRun", faceAuthLog)
                    .flowOn(mainDispatcher)
                    .stateIn(applicationScope, SharingStarted.Eagerly, false)

            // Face detection can run only when lockscreen bypass is enabled
            // & detection is supported
            //   & biometric unlock is not allowed
            //     or user is trusted by trust manager & we want to run face detect to dismiss
            // keyguard
            canRunDetection =
                listOf(
                        *gatingConditionsForAuthAndDetect(),
                        Pair(isBypassEnabled, "isBypassEnabled"),
                        Pair(
                            biometricSettingsRepository.isFaceAuthCurrentlyAllowed
                                .isFalse()
                                .or(trustRepository.isCurrentUserTrusted),
                            "faceAuthIsNotCurrentlyAllowedOrCurrentUserIsTrusted"
                        ),
                        // We don't want to run face detect if fingerprint can be used to unlock the
                        // device
                        // but it's not possible to authenticate with FP from the bouncer (UDFPS)
                        Pair(
                            and(isUdfps(), deviceEntryFingerprintAuthRepository.isRunning)
                                .isFalse(),
                            "udfpsAuthIsNotPossibleAnymore"
                        )
                    )
                    .andAllFlows("canFaceDetectRun", faceDetectLog)
                    .flowOn(mainDispatcher)
                    .stateIn(applicationScope, SharingStarted.Eagerly, false)
            observeFaceAuthGatingChecks()
            observeFaceDetectGatingChecks()
            observeFaceAuthResettingConditions()
            listenForSchedulingWatchdog()
        } else {
            canRunFaceAuth = MutableStateFlow(false).asStateFlow()
            canRunDetection = MutableStateFlow(false).asStateFlow()
        }
    }

@@ -298,39 +345,13 @@ constructor(
    }

    private fun observeFaceDetectGatingChecks() {
        // Face detection can run only when lockscreen bypass is enabled
        // & detection is supported
        //   & biometric unlock is not allowed
        //     or user is trusted by trust manager & we want to run face detect to dismiss keyguard
        listOf(
                canFaceAuthOrDetectRun(faceDetectLog),
                logAndObserve(isBypassEnabled, "isBypassEnabled", faceDetectLog),
                logAndObserve(
                    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
                // but it's not possible to authenticate with FP from the bouncer (UDFPS)
                logAndObserve(
                    and(isUdfps(), deviceEntryFingerprintAuthRepository.isRunning).isFalse(),
                    "udfpsAuthIsNotPossibleAnymore",
                    faceDetectLog
                )
            )
            .reduce(::and)
            .distinctUntilChanged()
        canRunDetection
            .onEach {
                faceAuthLogger.canRunDetectionChanged(it)
                canRunDetection.value = it
                if (!it) {
                    cancelDetection()
                }
            }
            .flowOn(mainDispatcher)
            .logDiffsForTable(faceDetectLog, "", "canFaceDetectRun", false)
            .launchIn(applicationScope)
    }

@@ -339,25 +360,19 @@ constructor(
            it == BiometricType.UNDER_DISPLAY_FINGERPRINT
        }

    private fun canFaceAuthOrDetectRun(tableLogBuffer: TableLogBuffer): Flow<Boolean> {
        return listOf(
                logAndObserve(
    private fun gatingConditionsForAuthAndDetect(): Array<Pair<Flow<Boolean>, String>> {
        return arrayOf(
            Pair(
                biometricSettingsRepository.isFaceAuthEnrolledAndEnabled,
                    "isFaceAuthEnrolledAndEnabled",
                    tableLogBuffer
                ),
                logAndObserve(faceAuthPaused.isFalse(), "faceAuthIsNotPaused", tableLogBuffer),
                logAndObserve(
                    keyguardRepository.isKeyguardGoingAway.isFalse(),
                    "keyguardNotGoingAway",
                    tableLogBuffer
                "isFaceAuthEnrolledAndEnabled"
            ),
                logAndObserve(
            Pair(faceAuthPaused.isFalse(), "faceAuthIsNotPaused"),
            Pair(keyguardRepository.isKeyguardGoingAway.isFalse(), "keyguardNotGoingAway"),
            Pair(
                keyguardRepository.wakefulness.map { it.isStartingToSleep() }.isFalse(),
                    "deviceNotStartingToSleep",
                    tableLogBuffer
                "deviceNotStartingToSleep"
            ),
                logAndObserve(
            Pair(
                keyguardInteractor.isSecureCameraActive
                    .isFalse()
                    .or(
@@ -365,50 +380,24 @@ constructor(
                            keyguardInteractor.primaryBouncerShowing
                        )
                    ),
                    "secureCameraNotActiveOrAnyBouncerIsShowing",
                    tableLogBuffer
                "secureCameraNotActiveOrAnyBouncerIsShowing"
            ),
                logAndObserve(
            Pair(
                biometricSettingsRepository.isFaceAuthSupportedInCurrentPosture,
                    "isFaceAuthSupportedInCurrentPosture",
                    tableLogBuffer
                "isFaceAuthSupportedInCurrentPosture"
            ),
                logAndObserve(
            Pair(
                biometricSettingsRepository.isCurrentUserInLockdown.isFalse(),
                    "userHasNotLockedDownDevice",
                    tableLogBuffer
                "userHasNotLockedDownDevice"
            ),
                logAndObserve(
                    keyguardRepository.isKeyguardShowing,
                    "isKeyguardShowing",
                    tableLogBuffer
                )
            Pair(keyguardRepository.isKeyguardShowing, "isKeyguardShowing")
        )
            .reduce(::and)
    }

    private fun observeFaceAuthGatingChecks() {
        // Face auth can run only if all of the gating conditions are true.
        listOf(
                canFaceAuthOrDetectRun(faceAuthLog),
                logAndObserve(isLockedOut.isFalse(), "isNotInLockOutState", faceAuthLog),
                logAndObserve(
                    trustRepository.isCurrentUserTrusted.isFalse(),
                    "currentUserIsNotTrusted",
                    faceAuthLog
                ),
                logAndObserve(
                    biometricSettingsRepository.isFaceAuthCurrentlyAllowed,
                    "isFaceAuthCurrentlyAllowed",
                    faceAuthLog
                ),
                logAndObserve(isAuthenticated.isFalse(), "faceNotAuthenticated", faceAuthLog),
            )
            .reduce(::and)
            .distinctUntilChanged()
        canRunFaceAuth
            .onEach {
                faceAuthLogger.canFaceAuthRunChanged(it)
                _canRunFaceAuth.value = it
                if (!it) {
                    // Cancel currently running auth if any of the gating checks are false.
                    faceAuthLogger.cancellingFaceAuth()
@@ -416,7 +405,6 @@ constructor(
                }
            }
            .flowOn(mainDispatcher)
            .logDiffsForTable(faceAuthLog, "", "canFaceAuthRun", false)
            .launchIn(applicationScope)
    }

@@ -618,22 +606,6 @@ constructor(
        _isAuthRunning.value = false
    }

    private fun logAndObserve(
        cond: Flow<Boolean>,
        conditionName: String,
        logBuffer: TableLogBuffer
    ): Flow<Boolean> {
        return cond
            .distinctUntilChanged()
            .logDiffsForTable(
                logBuffer,
                columnName = conditionName,
                columnPrefix = "",
                initialValue = false
            )
            .onEach { faceAuthLogger.observedConditionChanged(it, conditionName) }
    }

    companion object {
        const val TAG = "DeviceEntryFaceAuthRepository"

@@ -688,3 +660,18 @@ private fun Flow<Boolean>.or(anotherFlow: Flow<Boolean>) =
private fun Flow<Boolean>.isFalse(): Flow<Boolean> {
    return this.map { !it }
}

private fun List<Pair<Flow<Boolean>, String>>.andAllFlows(
    combinedLoggingInfo: String,
    tableLogBuffer: TableLogBuffer
): Flow<Boolean> {
    return combine(this.map { it.first }) {
        val combinedValue =
            it.reduceIndexed { index, accumulator, current ->
                tableLogBuffer.logChange(prefix = "", columnName = this[index].second, current)
                return@reduceIndexed accumulator && current
            }
        tableLogBuffer.logChange(prefix = "", combinedLoggingInfo, combinedValue)
        return@combine combinedValue
    }
}