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