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