• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.deviceentry.data.repository
18 
19 import android.app.StatusBarManager
20 import android.content.Context
21 import android.hardware.face.FaceManager
22 import android.os.CancellationSignal
23 import com.android.app.tracing.coroutines.launchTraced as launch
24 import com.android.internal.logging.InstanceId
25 import com.android.internal.logging.UiEventLogger
26 import com.android.systemui.Dumpable
27 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
28 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
29 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
30 import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
31 import com.android.systemui.dagger.SysUISingleton
32 import com.android.systemui.dagger.qualifiers.Application
33 import com.android.systemui.dagger.qualifiers.Background
34 import com.android.systemui.dagger.qualifiers.Main
35 import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
36 import com.android.systemui.deviceentry.shared.model.AcquiredFaceAuthenticationStatus
37 import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
38 import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus
39 import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus
40 import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
41 import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
42 import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
43 import com.android.systemui.dump.DumpManager
44 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
45 import com.android.systemui.keyguard.data.repository.BiometricType
46 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
47 import com.android.systemui.keyguard.data.repository.FaceAuthTableLog
48 import com.android.systemui.keyguard.data.repository.FaceDetectTableLog
49 import com.android.systemui.keyguard.data.repository.KeyguardRepository
50 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
51 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
52 import com.android.systemui.keyguard.shared.model.Edge
53 import com.android.systemui.keyguard.shared.model.KeyguardState
54 import com.android.systemui.keyguard.shared.model.StatusBarState
55 import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions
56 import com.android.systemui.keyguard.shared.model.TransitionState
57 import com.android.systemui.log.FaceAuthenticationLogger
58 import com.android.systemui.log.SessionTracker
59 import com.android.systemui.log.table.TableLogBuffer
60 import com.android.systemui.power.domain.interactor.PowerInteractor
61 import com.android.systemui.scene.domain.interactor.SceneInteractor
62 import com.android.systemui.scene.shared.flag.SceneContainerFlag
63 import com.android.systemui.scene.shared.model.Overlays
64 import com.android.systemui.scene.shared.model.Scenes
65 import com.android.systemui.statusbar.phone.KeyguardBypassController
66 import com.android.systemui.user.data.model.SelectionStatus
67 import com.android.systemui.user.data.repository.UserRepository
68 import com.google.errorprone.annotations.CompileTimeConstant
69 import java.io.PrintWriter
70 import java.util.concurrent.Executor
71 import javax.inject.Inject
72 import kotlinx.coroutines.CoroutineDispatcher
73 import kotlinx.coroutines.CoroutineScope
74 import kotlinx.coroutines.Job
75 import kotlinx.coroutines.channels.awaitClose
76 import kotlinx.coroutines.delay
77 import kotlinx.coroutines.flow.Flow
78 import kotlinx.coroutines.flow.MutableStateFlow
79 import kotlinx.coroutines.flow.SharingStarted
80 import kotlinx.coroutines.flow.StateFlow
81 import kotlinx.coroutines.flow.combine
82 import kotlinx.coroutines.flow.filter
83 import kotlinx.coroutines.flow.filterNotNull
84 import kotlinx.coroutines.flow.flowOf
85 import kotlinx.coroutines.flow.flowOn
86 import kotlinx.coroutines.flow.launchIn
87 import kotlinx.coroutines.flow.map
88 import kotlinx.coroutines.flow.merge
89 import kotlinx.coroutines.flow.onEach
90 import kotlinx.coroutines.flow.stateIn
91 import kotlinx.coroutines.withContext
92 
93 /**
94  * API to run face authentication and detection for device entry / on keyguard (as opposed to the
95  * biometric prompt).
96  */
97 interface DeviceEntryFaceAuthRepository {
98     /** Provide the current face authentication state for device entry. */
99     val isAuthenticated: StateFlow<Boolean>
100 
101     /** Whether face auth can run at this point. */
102     val canRunFaceAuth: StateFlow<Boolean>
103 
104     /** Provide the current status of face authentication. */
105     val authenticationStatus: Flow<FaceAuthenticationStatus>
106 
107     /** Provide the current status of face detection. */
108     val detectionStatus: Flow<FaceDetectionStatus>
109 
110     /** Current state of whether face authentication is locked out or not. */
111     val isLockedOut: StateFlow<Boolean>
112 
113     /** Current state of whether face authentication is running. */
114     val isAuthRunning: StateFlow<Boolean>
115 
116     /** Whether bypass is currently enabled */
117     val isBypassEnabled: Flow<Boolean>
118 
119     /** Set whether face authentication should be locked out or not */
120     fun setLockedOut(isLockedOut: Boolean)
121 
122     /**
123      * Request face authentication or detection to be run.
124      *
125      * [uiEvent] provided should be logged whenever face authentication runs. Invocation should be
126      * ignored if face authentication is already running. Results should be propagated through
127      * [authenticationStatus]
128      *
129      * Run only face detection when [fallbackToDetection] is true and [canRunFaceAuth] is false.
130      *
131      * Method returns immediately and the face auth request is processed as soon as possible.
132      */
133     fun requestAuthenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean = false)
134 
135     /** Stop currently running face authentication or detection. */
136     fun cancel()
137 }
138 
139 private data class AuthenticationRequest(
140     val uiEvent: FaceAuthUiEvent,
141     val fallbackToDetection: Boolean,
142 )
143 
144 @SysUISingleton
145 class DeviceEntryFaceAuthRepositoryImpl
146 @Inject
147 constructor(
148     context: Context,
149     private val faceManager: FaceManager? = null,
150     private val userRepository: UserRepository,
151     private val keyguardBypassController: KeyguardBypassController? = null,
152     @Application private val applicationScope: CoroutineScope,
153     @Main private val mainDispatcher: CoroutineDispatcher,
154     @Background private val backgroundDispatcher: CoroutineDispatcher,
155     @Background private val backgroundExecutor: Executor,
156     private val sessionTracker: SessionTracker,
157     private val uiEventsLogger: UiEventLogger,
158     private val faceAuthLogger: FaceAuthenticationLogger,
159     private val biometricSettingsRepository: BiometricSettingsRepository,
160     private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
161     private val keyguardRepository: KeyguardRepository,
162     private val powerInteractor: PowerInteractor,
163     private val keyguardInteractor: KeyguardInteractor,
164     private val alternateBouncerInteractor: AlternateBouncerInteractor,
165     private val sceneInteractor: dagger.Lazy<SceneInteractor>,
166     @FaceDetectTableLog private val faceDetectLog: TableLogBuffer,
167     @FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
168     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
169     private val displayStateInteractor: DisplayStateInteractor,
170     dumpManager: DumpManager,
171 ) : DeviceEntryFaceAuthRepository, Dumpable {
172     private var authCancellationSignal: CancellationSignal? = null
173     private var detectCancellationSignal: CancellationSignal? = null
174     private var retryCount = 0
175 
176     private var pendingAuthenticateRequest = MutableStateFlow<AuthenticationRequest?>(null)
177 
178     private var cancelNotReceivedHandlerJob: Job? = null
179     private var halErrorRetryJob: Job? = null
180 
181     private val _authenticationStatus: MutableStateFlow<FaceAuthenticationStatus?> =
182         MutableStateFlow(null)
183     override val authenticationStatus: Flow<FaceAuthenticationStatus>
184         get() = _authenticationStatus.filterNotNull()
185 
186     private val _detectionStatus = MutableStateFlow<FaceDetectionStatus?>(null)
187     override val detectionStatus: Flow<FaceDetectionStatus>
188         get() = _detectionStatus.filterNotNull()
189 
190     private val _isLockedOut = MutableStateFlow(false)
191     override val isLockedOut: StateFlow<Boolean> = _isLockedOut
192 
193     val isDetectionSupported =
194         faceManager?.sensorPropertiesInternal?.firstOrNull()?.supportsFaceDetection ?: false
195 
196     private val _isAuthRunning = MutableStateFlow(false)
197     override val isAuthRunning: StateFlow<Boolean>
198         get() = _isAuthRunning
199 
200     private val keyguardSessionId: InstanceId?
201         get() = sessionTracker.getSessionId(StatusBarManager.SESSION_KEYGUARD)
202 
203     override val canRunFaceAuth: StateFlow<Boolean>
204 
205     private val canRunDetection: StateFlow<Boolean>
206 
207     private val _isAuthenticated = MutableStateFlow(false)
208     override val isAuthenticated: StateFlow<Boolean> = _isAuthenticated
209 
210     private var cancellationInProgress = MutableStateFlow(false)
211 
212     override val isBypassEnabled: Flow<Boolean> =
<lambda>null213         keyguardBypassController?.let {
214             conflatedCallbackFlow {
215                 val callback =
216                     object : KeyguardBypassController.OnBypassStateChangedListener {
217                         override fun onBypassStateChanged(isEnabled: Boolean) {
218                             trySendWithFailureLogging(isEnabled, TAG, "BypassStateChanged")
219                         }
220                     }
221                 it.registerOnBypassStateChangedListener(callback)
222                 trySendWithFailureLogging(it.bypassEnabled, TAG, "BypassStateChanged")
223                 awaitClose { it.unregisterOnBypassStateChangedListener(callback) }
224             }
225         } ?: flowOf(false)
226 
setLockedOutnull227     override fun setLockedOut(isLockedOut: Boolean) {
228         _isLockedOut.value = isLockedOut
229     }
230 
231     private val faceLockoutResetCallback =
232         object : FaceManager.LockoutResetCallback() {
onLockoutResetnull233             override fun onLockoutReset(sensorId: Int) {
234                 _isLockedOut.value = false
235             }
236         }
237 
238     init {
<lambda>null239         backgroundExecutor.execute {
240             faceManager?.addLockoutResetCallback(faceLockoutResetCallback)
241             faceAuthLogger.addLockoutResetCallbackDone()
242         }
243         dumpManager.registerCriticalDumpable("DeviceEntryFaceAuthRepositoryImpl", this)
244 
245         canRunFaceAuth =
246             listOf(
247                     *gatingConditionsForAuthAndDetect(),
248                     Pair(isLockedOut.isFalse(), "isNotInLockOutState"),
249                     Pair(
250                         keyguardRepository.isKeyguardDismissible.isFalse(),
251                         "keyguardIsNotDismissible",
252                     ),
253                     Pair(
254                         biometricSettingsRepository.isFaceAuthCurrentlyAllowed,
255                         "isFaceAuthCurrentlyAllowed",
256                     ),
257                     Pair(isAuthenticated.isFalse(), "faceNotAuthenticated"),
258                 )
259                 .andAllFlows("canFaceAuthRun", faceAuthLog)
260                 .flowOn(backgroundDispatcher)
261                 .stateIn(applicationScope, SharingStarted.Eagerly, false)
262 
263         // Face detection can run only when lockscreen bypass is enabled
264         // & detection is supported
265         //   & biometric unlock is not allowed
266         //     or user is trusted by trust manager & we want to run face detect to dismiss
267         // keyguard
268         canRunDetection =
269             listOf(
270                     *gatingConditionsForAuthAndDetect(),
271                     Pair(isBypassEnabled, "isBypassEnabled"),
272                     Pair(
273                         biometricSettingsRepository.isFaceAuthCurrentlyAllowed
274                             .isFalse()
275                             .or(keyguardRepository.isKeyguardDismissible),
276                         "faceAuthIsNotCurrentlyAllowedOrCurrentUserIsTrusted",
277                     ),
278                     // We don't want to run face detect if fingerprint can be used to unlock the
279                     // device
280                     // but it's not possible to authenticate with FP from the bouncer (UDFPS)
281                     Pair(
282                         and(isUdfps(), deviceEntryFingerprintAuthRepository.isRunning).isFalse(),
283                         "udfpsAuthIsNotPossibleAnymore",
284                     ),
285                 )
286                 .andAllFlows("canFaceDetectRun", faceDetectLog)
287                 .flowOn(backgroundDispatcher)
288                 .stateIn(applicationScope, SharingStarted.Eagerly, false)
289         observeFaceAuthGatingChecks()
290         observeFaceDetectGatingChecks()
291         observeFaceAuthResettingConditions()
292         listenForSchedulingWatchdog()
293         processPendingAuthRequests()
294     }
295 
listenForSchedulingWatchdognull296     private fun listenForSchedulingWatchdog() {
297         keyguardTransitionInteractor
298             .transition(
299                 edge = Edge.create(to = Scenes.Gone),
300                 edgeWithoutSceneContainer = Edge.create(to = KeyguardState.GONE),
301             )
302             .filter { it.transitionState == TransitionState.FINISHED }
303             .onEach {
304                 // We deliberately want to run this in background because scheduleWatchdog does
305                 // a Binder IPC.
306                 withContext(backgroundDispatcher) {
307                     faceAuthLogger.watchdogScheduled()
308                     faceManager?.scheduleWatchdog()
309                 }
310             }
311             .launchIn(applicationScope)
312     }
313 
observeFaceAuthResettingConditionsnull314     private fun observeFaceAuthResettingConditions() {
315         // Clear auth status when keyguard done animations finished or when the user is switching
316         // or device starts going to sleep.
317         merge(
318                 powerInteractor.isAsleep,
319                 combine(
320                     keyguardTransitionInteractor.isFinishedIn(
321                         content = Scenes.Gone,
322                         stateWithoutSceneContainer = KeyguardState.GONE,
323                     ),
324                     keyguardInteractor.statusBarState,
325                 ) { isFinishedInGoneState, statusBarState ->
326                     // When the user is dragging the primary bouncer in (up) by manually scrolling
327                     // up on the lockscreen, the device won't be irreversibly transitioned to GONE
328                     // until the statusBarState updates to SHADE, so we check that here.
329                     // Else, we could reset the face auth state too early and end up in a strange
330                     // state.
331                     isFinishedInGoneState && statusBarState == StatusBarState.SHADE
332                 },
333                 userRepository.selectedUser.map {
334                     it.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
335                 },
336             )
337             .flowOn(mainDispatcher) // should revoke auth ASAP in the main thread
338             .onEach { anyOfThemIsTrue ->
339                 if (anyOfThemIsTrue) {
340                     clearPendingAuthRequest("Resetting auth status")
341                     _isAuthenticated.value = false
342                     retryCount = 0
343                     halErrorRetryJob?.cancel()
344                 }
345             }
346             .launchIn(applicationScope)
347     }
348 
clearPendingAuthRequestnull349     private fun clearPendingAuthRequest(@CompileTimeConstant loggingContext: String) {
350         faceAuthLogger.clearingPendingAuthRequest(
351             loggingContext,
352             pendingAuthenticateRequest.value?.uiEvent,
353             pendingAuthenticateRequest.value?.fallbackToDetection,
354         )
355         pendingAuthenticateRequest.value = null
356     }
357 
observeFaceDetectGatingChecksnull358     private fun observeFaceDetectGatingChecks() {
359         canRunDetection
360             .onEach {
361                 if (!it) {
362                     cancelDetection()
363                 }
364             }
365             .flowOn(mainDispatcher)
366             .launchIn(applicationScope)
367     }
368 
isUdfpsnull369     private fun isUdfps() =
370         deviceEntryFingerprintAuthRepository.availableFpSensorType.map {
371             it == BiometricType.UNDER_DISPLAY_FINGERPRINT
372         }
373 
gatingConditionsForAuthAndDetectnull374     private fun gatingConditionsForAuthAndDetect(): Array<Pair<Flow<Boolean>, String>> {
375         return arrayOf(
376             Pair(
377                 and(
378                         displayStateInteractor.isDefaultDisplayOff,
379                         keyguardTransitionInteractor.isFinishedInStateWhere(
380                             KeyguardState::deviceIsAwakeInState
381                         ),
382                     )
383                     .isFalse(),
384                 // this can happen if an app is requesting for screen off, the display can
385                 // turn off without wakefulness.isStartingToSleepOrAsleep calls
386                 "displayIsNotOffWhileFullyTransitionedToAwake",
387             ),
388             Pair(
389                 biometricSettingsRepository.isFaceAuthEnrolledAndEnabled,
390                 "isFaceAuthEnrolledAndEnabled",
391             ),
392             Pair(
393                 if (SceneContainerFlag.isEnabled) {
394                     sceneInteractor
395                         .get()
396                         .transitionState
397                         .map { it.isTransitioning(to = Scenes.Gone) || it.isIdle(Scenes.Gone) }
398                         .isFalse()
399                 } else {
400                     keyguardRepository.isKeyguardGoingAway.isFalse()
401                 },
402                 "keyguardNotGoingAway",
403             ),
404             Pair(
405                 keyguardTransitionInteractor
406                     .isInTransitionWhere(toStatePredicate = KeyguardState::deviceIsAsleepInState)
407                     .isFalse(),
408                 "deviceNotTransitioningToAsleepState",
409             ),
410             Pair(
411                 keyguardInteractor.isSecureCameraActive
412                     .isFalse()
413                     .or(
414                         alternateBouncerInteractor.isVisible.or(
415                             if (SceneContainerFlag.isEnabled) {
416                                 sceneInteractor.get().transitionState.map {
417                                     it.isIdle(overlay = Overlays.Bouncer)
418                                 }
419                             } else {
420                                 keyguardInteractor.primaryBouncerShowing
421                             }
422                         )
423                     ),
424                 "secureCameraNotActiveOrAnyBouncerIsShowing",
425             ),
426             Pair(
427                 biometricSettingsRepository.isFaceAuthSupportedInCurrentPosture,
428                 "isFaceAuthSupportedInCurrentPosture",
429             ),
430             Pair(
431                 biometricSettingsRepository.isCurrentUserInLockdown.isFalse(),
432                 "userHasNotLockedDownDevice",
433             ),
434             Pair(keyguardRepository.isKeyguardShowing, "isKeyguardShowing"),
435             Pair(
436                 userRepository.selectedUser
437                     .map { it.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS }
438                     .isFalse(),
439                 "userSwitchingInProgress",
440             ),
441         )
442     }
443 
observeFaceAuthGatingChecksnull444     private fun observeFaceAuthGatingChecks() {
445         canRunFaceAuth
446             .onEach {
447                 faceAuthLogger.canFaceAuthRunChanged(it)
448                 if (!it) {
449                     // Cancel currently running auth if any of the gating checks are false.
450                     faceAuthLogger.cancellingFaceAuth()
451                     cancel()
452                 }
453             }
454             .flowOn(mainDispatcher)
455             .launchIn(applicationScope)
456     }
457 
458     private val faceAuthCallback =
459         object : FaceManager.AuthenticationCallback() {
onAuthenticationFailednull460             override fun onAuthenticationFailed() {
461                 _isAuthenticated.value = false
462                 faceAuthLogger.authenticationFailed()
463                 _authenticationStatus.value = FailedFaceAuthenticationStatus()
464                 if (!_isLockedOut.value) {
465                     // onAuthenticationError gets invoked before onAuthenticationFailed when the
466                     // last auth attempt locks out face authentication.
467                     // Skip onFaceAuthRequestCompleted in such a scenario.
468                     onFaceAuthRequestCompleted()
469                 }
470             }
471 
onAuthenticationAcquirednull472             override fun onAuthenticationAcquired(acquireInfo: Int) {
473                 _authenticationStatus.value = AcquiredFaceAuthenticationStatus(acquireInfo)
474             }
475 
onAuthenticationErrornull476             override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
477                 val errorStatus = ErrorFaceAuthenticationStatus(errorCode, errString.toString())
478                 if (errorStatus.isLockoutError()) {
479                     _isLockedOut.value = true
480                 }
481                 _isAuthenticated.value = false
482                 _authenticationStatus.value = errorStatus
483                 if (errorStatus.isHardwareError()) {
484                     faceAuthLogger.hardwareError(errorStatus)
485                     handleFaceHardwareError()
486                 }
487                 faceAuthLogger.authenticationError(
488                     errorCode,
489                     errString,
490                     errorStatus.isLockoutError(),
491                     errorStatus.isCancellationError(),
492                 )
493                 onFaceAuthRequestCompleted()
494             }
495 
onAuthenticationHelpnull496             override fun onAuthenticationHelp(code: Int, helpStr: CharSequence?) {
497                 _authenticationStatus.value =
498                     HelpFaceAuthenticationStatus(code, helpStr?.toString())
499             }
500 
onAuthenticationSucceedednull501             override fun onAuthenticationSucceeded(result: FaceManager.AuthenticationResult) {
502                 // Update _isAuthenticated before _authenticationStatus is updated. There are
503                 // consumers that receive the face authentication updates through a long chain of
504                 // callbacks
505                 // _authenticationStatus -> KeyguardUpdateMonitor -> KeyguardStateController ->
506                 // onUnlockChanged
507                 // These consumers then query the isAuthenticated boolean. This makes sure that the
508                 // boolean is updated to new value before the event is propagated.
509                 // TODO (b/310592822): once all consumers can use the new system directly, we don't
510                 //  have to worry about this ordering.
511                 _isAuthenticated.value = true
512                 _authenticationStatus.value = SuccessFaceAuthenticationStatus(result)
513                 faceAuthLogger.faceAuthSuccess(result)
514                 onFaceAuthRequestCompleted()
515             }
516         }
517 
handleFaceHardwareErrornull518     private fun handleFaceHardwareError() {
519         if (retryCount < HAL_ERROR_RETRY_MAX) {
520             retryCount++
521             halErrorRetryJob?.cancel()
522             halErrorRetryJob =
523                 applicationScope.launch {
524                     delay(HAL_ERROR_RETRY_TIMEOUT)
525                     if (retryCount < HAL_ERROR_RETRY_MAX) {
526                         faceAuthLogger.attemptingRetryAfterHardwareError(retryCount)
527                         requestAuthenticate(
528                             FaceAuthUiEvent.FACE_AUTH_TRIGGERED_RETRY_AFTER_HW_UNAVAILABLE,
529                             fallbackToDetection = false,
530                         )
531                     }
532                 }
533         }
534     }
535 
onFaceAuthRequestCompletednull536     private fun onFaceAuthRequestCompleted() {
537         cancelNotReceivedHandlerJob?.cancel()
538         _isAuthRunning.value = false
539         authCancellationSignal = null
540         // Updates to "cancellationInProgress" may re-trigger face auth
541         // (see processPendingAuthRequests()), so we must update this after setting _isAuthRunning
542         // to false.
543         cancellationInProgress.value = false
544     }
545 
546     private val detectionCallback =
isStrongnull547         FaceManager.FaceDetectionCallback { sensorId, userId, isStrong ->
548             faceAuthLogger.faceDetected()
549             _detectionStatus.value = FaceDetectionStatus(sensorId, userId, isStrong)
550         }
551 
requestAuthenticatenull552     override fun requestAuthenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
553         if (pendingAuthenticateRequest.value != null) {
554             faceAuthLogger.ignoredFaceAuthTrigger(
555                 pendingAuthenticateRequest.value?.uiEvent,
556                 "Previously queued trigger skipped due to new request",
557             )
558         }
559         faceAuthLogger.queueingRequest(uiEvent, fallbackToDetection)
560         pendingAuthenticateRequest.value = AuthenticationRequest(uiEvent, fallbackToDetection)
561     }
562 
processPendingAuthRequestsnull563     private fun processPendingAuthRequests() {
564         combine(
565                 pendingAuthenticateRequest,
566                 canRunFaceAuth,
567                 canRunDetection,
568                 cancellationInProgress,
569             ) { pending, canRunAuth, canRunDetect, cancelInProgress ->
570                 if (
571                     pending != null &&
572                         !(canRunAuth || (canRunDetect && pending.fallbackToDetection)) ||
573                         cancelInProgress
574                 ) {
575                     faceAuthLogger.notProcessingRequestYet(
576                         pending?.uiEvent,
577                         canRunAuth,
578                         canRunDetect,
579                         cancelInProgress,
580                     )
581                     return@combine null
582                 } else {
583                     return@combine pending
584                 }
585             }
586             .onEach {
587                 it?.let {
588                     faceAuthLogger.processingRequest(it.uiEvent, it.fallbackToDetection)
589                     clearPendingAuthRequest("Authenticate was invoked")
590                     authenticate(it.uiEvent, it.fallbackToDetection)
591                 }
592             }
593             .flowOn(mainDispatcher)
594             .launchIn(applicationScope)
595     }
596 
authenticatenull597     private suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
598         if (_isAuthRunning.value) {
599             faceAuthLogger.ignoredFaceAuthTrigger(uiEvent, "face auth is currently running")
600             return
601         }
602 
603         if (cancellationInProgress.value) {
604             faceAuthLogger.ignoredFaceAuthTrigger(uiEvent, "cancellation in progress")
605             return
606         }
607 
608         if (canRunFaceAuth.value) {
609             withContext(mainDispatcher) {
610                 // We always want to invoke face auth in the main thread.
611                 authCancellationSignal = CancellationSignal()
612                 _isAuthRunning.value = true
613                 uiEventsLogger.logWithInstanceIdAndPosition(
614                     uiEvent,
615                     0,
616                     null,
617                     keyguardSessionId,
618                     uiEvent.extraInfo,
619                 )
620                 faceAuthLogger.authenticating(uiEvent)
621                 faceManager?.authenticate(
622                     null,
623                     authCancellationSignal,
624                     faceAuthCallback,
625                     null,
626                     SysUiFaceAuthenticateOptions(
627                             currentUserId,
628                             uiEvent,
629                             wakeReason = uiEvent.extraInfo,
630                         )
631                         .toFaceAuthenticateOptions(),
632                 )
633             }
634         } else if (canRunDetection.value) {
635             if (fallbackToDetection) {
636                 faceAuthLogger.ignoredFaceAuthTrigger(
637                     uiEvent,
638                     "face auth gating check is false, falling back to detection.",
639                 )
640                 detect(uiEvent)
641             } else {
642                 faceAuthLogger.ignoredFaceAuthTrigger(
643                     uiEvent = uiEvent,
644                     "face auth gating check is false and fallback to detection is not requested",
645                 )
646             }
647         } else {
648             faceAuthLogger.ignoredFaceAuthTrigger(
649                 uiEvent,
650                 "face auth & detect gating check is false",
651             )
652         }
653     }
654 
detectnull655     suspend fun detect(uiEvent: FaceAuthUiEvent) {
656         if (!isDetectionSupported) {
657             faceAuthLogger.detectionNotSupported(faceManager, faceManager?.sensorPropertiesInternal)
658             return
659         }
660         if (_isAuthRunning.value) {
661             faceAuthLogger.skippingDetection(_isAuthRunning.value, detectCancellationSignal != null)
662             return
663         }
664         withContext(mainDispatcher) {
665             // We always want to invoke face detect in the main thread.
666             faceAuthLogger.faceDetectionStarted()
667             detectCancellationSignal?.cancel()
668             detectCancellationSignal = CancellationSignal()
669             detectCancellationSignal?.let {
670                 faceManager?.detectFace(
671                     it,
672                     detectionCallback,
673                     SysUiFaceAuthenticateOptions(currentUserId, uiEvent, uiEvent.extraInfo)
674                         .toFaceAuthenticateOptions(),
675                 )
676             }
677         }
678     }
679 
680     private val currentUserId: Int
681         get() = userRepository.getSelectedUserInfo().id
682 
cancelDetectionnull683     private fun cancelDetection() {
684         detectCancellationSignal?.cancel()
685         detectCancellationSignal = null
686     }
687 
cancelnull688     override fun cancel() {
689         if (authCancellationSignal == null) return
690 
691         authCancellationSignal?.cancel()
692         cancelNotReceivedHandlerJob?.cancel()
693         cancelNotReceivedHandlerJob =
694             applicationScope.launch {
695                 delay(DEFAULT_CANCEL_SIGNAL_TIMEOUT)
696                 faceAuthLogger.cancelSignalNotReceived(
697                     _isAuthRunning.value,
698                     _isLockedOut.value,
699                     cancellationInProgress.value,
700                     pendingAuthenticateRequest.value?.uiEvent,
701                 )
702                 _authenticationStatus.value = ErrorFaceAuthenticationStatus.cancelNotReceivedError()
703                 onFaceAuthRequestCompleted()
704             }
705         cancellationInProgress.value = true
706         _isAuthRunning.value = false
707     }
708 
709     companion object {
710         const val TAG = "DeviceEntryFaceAuthRepository"
711 
712         /**
713          * If no cancel signal has been received after this amount of time, assume that it is
714          * cancelled.
715          */
716         const val DEFAULT_CANCEL_SIGNAL_TIMEOUT = 3000L
717 
718         /** Number of allowed retries whenever there is a face hardware error */
719         const val HAL_ERROR_RETRY_MAX = 5
720 
721         /** Timeout before retries whenever there is a HAL error. */
722         const val HAL_ERROR_RETRY_TIMEOUT = 500L // ms
723     }
724 
dumpnull725     override fun dump(pw: PrintWriter, args: Array<out String>) {
726         pw.println("DeviceEntryFaceAuthRepositoryImpl state:")
727         pw.println("  cancellationInProgress: $cancellationInProgress")
728         pw.println("  _isLockedOut.value: ${_isLockedOut.value}")
729         pw.println("  _isAuthRunning.value: ${_isAuthRunning.value}")
730         pw.println("  isDetectionSupported: $isDetectionSupported")
731         pw.println("  FaceManager state:")
732         pw.println("    faceManager: $faceManager")
733         pw.println("    sensorPropertiesInternal: ${faceManager?.sensorPropertiesInternal}")
734         pw.println(
735             "    supportsFaceDetection: " +
736                 "${faceManager?.sensorPropertiesInternal?.firstOrNull()?.supportsFaceDetection}"
737         )
738         pw.println("  _pendingAuthenticateRequest: ${pendingAuthenticateRequest.value}")
739         pw.println("  authCancellationSignal: $authCancellationSignal")
740         pw.println("  detectCancellationSignal: $detectCancellationSignal")
741         pw.println("  _authenticationStatus: ${_authenticationStatus.value}")
742         pw.println("  _detectionStatus: ${_detectionStatus.value}")
743         pw.println("  currentUserId: $currentUserId")
744         pw.println("  keyguardSessionId: $keyguardSessionId")
745         pw.println("  lockscreenBypassEnabled: ${keyguardBypassController?.bypassEnabled ?: false}")
746     }
747 }
748 
749 /** Combine two boolean flows by and-ing both of them */
andnull750 private fun and(flow: Flow<Boolean>, anotherFlow: Flow<Boolean>) =
751     flow.combine(anotherFlow) { a, b -> a && b }
752 
753 /** Combine two boolean flows by or-ing both of them */
ornull754 private fun Flow<Boolean>.or(anotherFlow: Flow<Boolean>) =
755     this.combine(anotherFlow) { a, b -> a || b }
756 
757 /** "Not" the given flow. The return [Flow] will be true when [this] flow is false. */
isFalsenull758 private fun Flow<Boolean>.isFalse(): Flow<Boolean> {
759     return this.map { !it }
760 }
761 
Listnull762 private fun List<Pair<Flow<Boolean>, String>>.andAllFlows(
763     combinedLoggingInfo: String,
764     tableLogBuffer: TableLogBuffer,
765 ): Flow<Boolean> {
766     return combine(this.map { it.first }) {
767         val combinedValue =
768             it.reduceIndexed { index, accumulator, current ->
769                 tableLogBuffer.logChange(prefix = "", columnName = this[index].second, current)
770                 return@reduceIndexed accumulator && current
771             }
772         tableLogBuffer.logChange(prefix = "", combinedLoggingInfo, combinedValue)
773         return@combine combinedValue
774     }
775 }
776