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

Commit aef7b742 authored by Chandru S's avatar Chandru S Committed by Android (Google) Code Review
Browse files

Merge "Queue face auth requests instead of running them immediately" into main

parents ac4d4168 82a1bf09
Loading
Loading
Loading
Loading
+98 −65
Original line number Diff line number Diff line
@@ -54,13 +54,13 @@ import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.UserRepository
import com.google.errorprone.annotations.CompileTimeConstant
import java.io.PrintWriter
import java.util.Arrays
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
@@ -112,33 +112,27 @@ interface DeviceEntryFaceAuthRepository {
    fun setLockedOut(isLockedOut: Boolean)

    /**
     * Cancel current face authentication and prevent it from running until [resumeFaceAuth] is
     * invoked.
     */
    fun pauseFaceAuth()

    /**
     * Allow face auth paused using [pauseFaceAuth] to run again. The next invocation to
     * [authenticate] will run as long as other gating conditions don't stop it from running.
     */
    fun resumeFaceAuth()

    /**
     * Trigger face authentication.
     * Request face authentication or detection to be run.
     *
     * [uiEvent] provided should be logged whenever face authentication runs. Invocation should be
     * ignored if face authentication is already running. Results should be propagated through
     * [authenticationStatus]
     *
     * Run only face detection when [fallbackToDetection] is true and [canRunFaceAuth] is false.
     *
     * Method returns immediately and the face auth request is processed as soon as possible.
     */
    suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean = false)
    fun requestAuthenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean = false)

    /** Stop currently running face authentication or detection. */
    fun cancel()
}

@OptIn(ExperimentalCoroutinesApi::class)
private data class AuthenticationRequest(
    val uiEvent: FaceAuthUiEvent,
    val fallbackToDetection: Boolean
)

@SysUISingleton
class DeviceEntryFaceAuthRepositoryImpl
@Inject
@@ -171,6 +165,8 @@ constructor(
    private var faceAcquiredInfoIgnoreList: Set<Int>
    private var retryCount = 0

    private var pendingAuthenticateRequest = MutableStateFlow<AuthenticationRequest?>(null)

    private var cancelNotReceivedHandlerJob: Job? = null
    private var halErrorRetryJob: Job? = null

@@ -193,15 +189,6 @@ constructor(
    override val isAuthRunning: StateFlow<Boolean>
        get() = _isAuthRunning

    private val faceAuthPaused = MutableStateFlow(false)
    override fun pauseFaceAuth() {
        faceAuthPaused.value = true
    }

    override fun resumeFaceAuth() {
        faceAuthPaused.value = false
    }

    private val keyguardSessionId: InstanceId?
        get() = sessionTracker.getSessionId(StatusBarManager.SESSION_KEYGUARD)

@@ -213,6 +200,8 @@ constructor(
    override val isAuthenticated: Flow<Boolean>
        get() = _isAuthenticated

    private var cancellationInProgress = MutableStateFlow(false)

    override val isBypassEnabled: Flow<Boolean> =
        keyguardBypassController?.let {
            conflatedCallbackFlow {
@@ -302,6 +291,7 @@ constructor(
            observeFaceDetectGatingChecks()
            observeFaceAuthResettingConditions()
            listenForSchedulingWatchdog()
            processPendingAuthRequests()
        } else {
            canRunFaceAuth = MutableStateFlow(false).asStateFlow()
            canRunDetection = MutableStateFlow(false).asStateFlow()
@@ -338,6 +328,7 @@ constructor(
            )
            .onEach { anyOfThemIsTrue ->
                if (anyOfThemIsTrue) {
                    clearPendingAuthRequest("Resetting auth status")
                    _isAuthenticated.value = false
                    retryCount = 0
                    halErrorRetryJob?.cancel()
@@ -346,6 +337,15 @@ constructor(
            .launchIn(applicationScope)
    }

    private fun clearPendingAuthRequest(@CompileTimeConstant loggingContext: String) {
        faceAuthLogger.clearingPendingAuthRequest(
            loggingContext,
            pendingAuthenticateRequest.value?.uiEvent,
            pendingAuthenticateRequest.value?.fallbackToDetection
        )
        pendingAuthenticateRequest.value = null
    }

    private fun observeFaceDetectGatingChecks() {
        canRunDetection
            .onEach {
@@ -378,7 +378,6 @@ constructor(
                biometricSettingsRepository.isFaceAuthEnrolledAndEnabled,
                "isFaceAuthEnrolledAndEnabled"
            ),
            Pair(faceAuthPaused.isFalse(), "faceAuthIsNotPaused"),
            Pair(keyguardRepository.isKeyguardGoingAway.isFalse(), "keyguardNotGoingAway"),
            Pair(
                keyguardRepository.wakefulness.map { it.isStartingToSleep() }.isFalse(),
@@ -402,7 +401,13 @@ constructor(
                biometricSettingsRepository.isCurrentUserInLockdown.isFalse(),
                "userHasNotLockedDownDevice"
            ),
            Pair(keyguardRepository.isKeyguardShowing, "isKeyguardShowing")
            Pair(keyguardRepository.isKeyguardShowing, "isKeyguardShowing"),
            Pair(
                userRepository.selectedUser
                    .map { it.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS }
                    .isFalse(),
                "userSwitchingInProgress"
            )
        )
    }

@@ -440,9 +445,6 @@ constructor(
                }
                _authenticationStatus.value = errorStatus
                _isAuthenticated.value = false
                if (errorStatus.isCancellationError()) {
                    handleFaceCancellationError()
                }
                if (errorStatus.isHardwareError()) {
                    faceAuthLogger.hardwareError(errorStatus)
                    handleFaceHardwareError()
@@ -471,16 +473,6 @@ constructor(
            }
        }

    private fun handleFaceCancellationError() {
        applicationScope.launch {
            faceAuthRequestedWhileCancellation?.let {
                faceAuthLogger.launchingQueuedFaceAuthRequest(it)
                authenticate(it)
            }
            faceAuthRequestedWhileCancellation = null
        }
    }

    private fun handleFaceHardwareError() {
        if (retryCount < HAL_ERROR_RETRY_MAX) {
            retryCount++
@@ -490,7 +482,7 @@ constructor(
                    delay(HAL_ERROR_RETRY_TIMEOUT)
                    if (retryCount < HAL_ERROR_RETRY_MAX) {
                        faceAuthLogger.attemptingRetryAfterHardwareError(retryCount)
                        authenticate(
                        requestAuthenticate(
                            FaceAuthUiEvent.FACE_AUTH_TRIGGERED_RETRY_AFTER_HW_UNAVAILABLE,
                            fallbackToDetection = false
                        )
@@ -501,7 +493,7 @@ constructor(

    private fun onFaceAuthRequestCompleted() {
        cancelNotReceivedHandlerJob?.cancel()
        cancellationInProgress = false
        cancellationInProgress.value = false
        _isAuthRunning.value = false
        authCancellationSignal = null
    }
@@ -512,24 +504,60 @@ constructor(
            _detectionStatus.value = FaceDetectionStatus(sensorId, userId, isStrong)
        }

    private var cancellationInProgress = false
    private var faceAuthRequestedWhileCancellation: FaceAuthUiEvent? = null
    override fun requestAuthenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
        if (pendingAuthenticateRequest.value != null) {
            faceAuthLogger.ignoredFaceAuthTrigger(
                pendingAuthenticateRequest.value?.uiEvent,
                "Previously queued trigger skipped due to new request"
            )
        }
        faceAuthLogger.queueingRequest(uiEvent, fallbackToDetection)
        pendingAuthenticateRequest.value = AuthenticationRequest(uiEvent, fallbackToDetection)
    }

    private fun processPendingAuthRequests() {
        combine(
                pendingAuthenticateRequest,
                canRunFaceAuth,
                canRunDetection,
                cancellationInProgress,
            ) { pending, canRunAuth, canRunDetect, cancelInProgress ->
                if (
                    pending != null &&
                        !(canRunAuth || (canRunDetect && pending.fallbackToDetection)) ||
                        cancelInProgress
                ) {
                    faceAuthLogger.notProcessingRequestYet(
                        pending?.uiEvent,
                        canRunAuth,
                        canRunDetect,
                        cancelInProgress
                    )
                    return@combine null
                } else {
                    return@combine pending
                }
            }
            .onEach {
                it?.let {
                    faceAuthLogger.processingRequest(it.uiEvent, it.fallbackToDetection)
                    clearPendingAuthRequest("Authenticate was invoked")
                    authenticate(it.uiEvent, it.fallbackToDetection)
                }
            }
            .flowOn(mainDispatcher)
            .launchIn(applicationScope)
    }

    override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
    private suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
        if (_isAuthRunning.value) {
            faceAuthLogger.ignoredFaceAuthTrigger(uiEvent, "face auth is currently running")
            return
        }

        if (cancellationInProgress) {
            faceAuthLogger.queuingRequestWhileCancelling(
                faceAuthRequestedWhileCancellation,
                uiEvent
            )
            faceAuthRequestedWhileCancellation = uiEvent
        if (cancellationInProgress.value) {
            faceAuthLogger.ignoredFaceAuthTrigger(uiEvent, "cancellation in progress")
            return
        } else {
            faceAuthRequestedWhileCancellation = null
        }

        if (canRunFaceAuth.value) {
@@ -553,12 +581,19 @@ constructor(
                    FaceAuthenticateOptions.Builder().setUserId(currentUserId).build()
                )
            }
        } else if (fallbackToDetection && canRunDetection.value) {
        } else if (canRunDetection.value) {
            if (fallbackToDetection) {
                faceAuthLogger.ignoredFaceAuthTrigger(
                    uiEvent,
                    "face auth gating check is false, falling back to detection."
                )
                detect()
            } else {
                faceAuthLogger.ignoredFaceAuthTrigger(
                    uiEvent = uiEvent,
                    "face auth gating check is false and fallback to detection is not requested"
                )
            }
        } else {
            faceAuthLogger.ignoredFaceAuthTrigger(
                uiEvent,
@@ -608,13 +643,13 @@ constructor(
                faceAuthLogger.cancelSignalNotReceived(
                    _isAuthRunning.value,
                    _isLockedOut.value,
                    cancellationInProgress,
                    faceAuthRequestedWhileCancellation
                    cancellationInProgress.value,
                    pendingAuthenticateRequest.value?.uiEvent
                )
                _authenticationStatus.value = ErrorFaceAuthenticationStatus.cancelNotReceivedError()
                onFaceAuthRequestCompleted()
            }
        cancellationInProgress = true
        cancellationInProgress.value = true
        _isAuthRunning.value = false
    }

@@ -647,9 +682,7 @@ constructor(
            "    supportsFaceDetection: " +
                "${faceManager?.sensorPropertiesInternal?.firstOrNull()?.supportsFaceDetection}"
        )
        pw.println(
            "  faceAuthRequestedWhileCancellation: ${faceAuthRequestedWhileCancellation?.reason}"
        )
        pw.println("  _pendingAuthenticateRequest: ${pendingAuthenticateRequest.value}")
        pw.println("  authCancellationSignal: $authCancellationSignal")
        pw.println("  detectCancellationSignal: $detectCancellationSignal")
        pw.println("  faceAcquiredInfoIgnoreList: $faceAcquiredInfoIgnoreList")
+1 −4
Original line number Diff line number Diff line
@@ -56,9 +56,6 @@ class NoopDeviceEntryFaceAuthRepository @Inject constructor() : DeviceEntryFaceA
        get() = emptyFlow()

    override fun setLockedOut(isLockedOut: Boolean) = Unit
    override fun pauseFaceAuth() = Unit

    override fun resumeFaceAuth() = Unit

    /**
     * Trigger face authentication.
@@ -69,7 +66,7 @@ class NoopDeviceEntryFaceAuthRepository @Inject constructor() : DeviceEntryFaceA
     *
     * Run only face detection when [fallbackToDetection] is true and [canRunFaceAuth] is false.
     */
    override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {}
    override fun requestAuthenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) = Unit

    /** Stop currently running face authentication or detection. */
    override fun cancel() {}
+3 −12
Original line number Diff line number Diff line
@@ -52,8 +52,6 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.yield

/**
@@ -144,14 +142,11 @@ constructor(
            .onEach { (previous, curr) ->
                val wasSwitching = previous.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
                val isSwitching = curr.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
                if (!wasSwitching && isSwitching) {
                    repository.pauseFaceAuth()
                } else if (wasSwitching && !isSwitching) {
                if (wasSwitching && !isSwitching) {
                    val lockoutMode = facePropertyRepository.getLockoutMode(curr.userInfo.id)
                    repository.setLockedOut(
                        lockoutMode == LockoutMode.PERMANENT || lockoutMode == LockoutMode.TIMED
                    )
                    repository.resumeFaceAuth()
                    yield()
                    runFaceAuth(
                        FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING,
@@ -232,12 +227,8 @@ constructor(
                    )
            } else {
                faceAuthenticationStatusOverride.value = null
                applicationScope.launch {
                    withContext(mainDispatcher) {
                faceAuthenticationLogger.authRequested(uiEvent)
                        repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect)
                    }
                }
                repository.requestAuthenticate(uiEvent, fallbackToDetection = fallbackToDetect)
            }
        } else {
            faceAuthenticationLogger.ignoredFaceAuthTrigger(
+76 −51
Original line number Diff line number Diff line
@@ -29,36 +29,18 @@ class FaceAuthenticationLogger
constructor(
    @FaceAuthLog private val logBuffer: LogBuffer,
) {
    fun ignoredFaceAuthTrigger(uiEvent: FaceAuthUiEvent, ignoredReason: String) {
    fun ignoredFaceAuthTrigger(uiEvent: FaceAuthUiEvent?, ignoredReason: String) {
        logBuffer.log(
            TAG,
            DEBUG,
            {
                str1 = uiEvent.reason
                str1 = "${uiEvent?.reason}"
                str2 = ignoredReason
            },
            { "Ignoring trigger because $str2, Trigger reason: $str1" }
        )
    }

    fun queuingRequestWhileCancelling(
        alreadyQueuedRequest: FaceAuthUiEvent?,
        newRequest: FaceAuthUiEvent
    ) {
        logBuffer.log(
            TAG,
            DEBUG,
            {
                str1 = alreadyQueuedRequest?.reason
                str2 = newRequest.reason
            },
            {
                "Face auth requested while previous request is being cancelled, " +
                    "already queued request: $str1 queueing the new request: $str2"
            }
        )
    }

    fun authenticating(uiEvent: FaceAuthUiEvent) {
        logBuffer.log(TAG, DEBUG, { str1 = uiEvent.reason }, { "Running authenticate for $str1" })
    }
@@ -161,15 +143,6 @@ constructor(
        )
    }

    fun launchingQueuedFaceAuthRequest(faceAuthRequestedWhileCancellation: FaceAuthUiEvent?) {
        logBuffer.log(
            TAG,
            DEBUG,
            { str1 = "${faceAuthRequestedWhileCancellation?.reason}" },
            { "Received cancellation error and starting queued face auth request: $str1" }
        )
    }

    fun faceAuthSuccess(result: FaceManager.AuthenticationResult) {
        logBuffer.log(
            TAG,
@@ -182,31 +155,10 @@ constructor(
        )
    }

    fun observedConditionChanged(newValue: Boolean, context: String) {
        logBuffer.log(
            TAG,
            DEBUG,
            {
                bool1 = newValue
                str1 = context
            },
            { "Observed condition changed: $str1, new value: $bool1" }
        )
    }

    fun canFaceAuthRunChanged(canRun: Boolean) {
        logBuffer.log(TAG, DEBUG, { bool1 = canRun }, { "canFaceAuthRun value changed to $bool1" })
    }

    fun canRunDetectionChanged(canRunDetection: Boolean) {
        logBuffer.log(
            TAG,
            DEBUG,
            { bool1 = canRunDetection },
            { "canRunDetection value changed to $bool1" }
        )
    }

    fun cancellingFaceAuth() {
        logBuffer.log(TAG, DEBUG, "cancelling face auth because a gating condition became false")
    }
@@ -236,7 +188,7 @@ constructor(
        logBuffer.log(
            TAG,
            DEBUG,
            { str1 = "$uiEvent" },
            { str1 = uiEvent.reason },
            { "Requesting face auth for trigger: $str1" }
        )
    }
@@ -269,4 +221,77 @@ constructor(
    fun faceLockedOut(@CompileTimeConstant reason: String) {
        logBuffer.log(TAG, DEBUG, "Face auth has been locked out: $reason")
    }

    fun queueingRequest(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
        logBuffer.log(
            TAG,
            DEBUG,
            {
                str1 = "$uiEvent"
                bool1 = fallbackToDetection
            },
            { "Queueing $str1 request for face auth, fallbackToDetection: $bool1" }
        )
    }

    fun notProcessingRequestYet(
        uiEvent: FaceAuthUiEvent?,
        canRunAuth: Boolean,
        canRunDetect: Boolean,
        cancelInProgress: Boolean
    ) {
        uiEvent?.let {
            logBuffer.log(
                TAG,
                DEBUG,
                {
                    str1 = uiEvent.reason
                    bool1 = canRunAuth
                    bool2 = canRunDetect
                    bool3 = cancelInProgress
                },
                {
                    "Waiting to process request: reason: $str1, " +
                        "canRunAuth: $bool1, " +
                        "canRunDetect: $bool2, " +
                        "cancelInProgress: $bool3"
                }
            )
        }
    }

    fun processingRequest(uiEvent: FaceAuthUiEvent?, fallbackToDetection: Boolean) {
        logBuffer.log(
            TAG,
            DEBUG,
            {
                str1 = "${uiEvent?.reason}"
                bool1 = fallbackToDetection
            },
            { "Processing face auth request: $str1, fallbackToDetect: $bool1" }
        )
    }

    fun clearingPendingAuthRequest(
        @CompileTimeConstant loggingContext: String,
        uiEvent: FaceAuthUiEvent?,
        fallbackToDetection: Boolean?
    ) {
        uiEvent?.let {
            logBuffer.log(
                TAG,
                DEBUG,
                {
                    str1 = uiEvent.reason
                    str2 = "$fallbackToDetection"
                    str3 = loggingContext
                },
                {
                    "Clearing pending auth: $str1, " +
                        "fallbackToDetection: $str2, " +
                        "reason: $str3"
                }
            )
        }
    }
}
+95 −45

File changed.

Preview size limit exceeded, changes collapsed.

Loading