1 /*
<lambda>null2 * Copyright (C) 2023 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.keyguard.data.repository
18
19 import android.app.StatusBarManager
20 import android.content.Context
21 import android.hardware.face.FaceAuthenticateOptions
22 import android.hardware.face.FaceManager
23 import android.os.CancellationSignal
24 import com.android.internal.logging.InstanceId
25 import com.android.internal.logging.UiEventLogger
26 import com.android.keyguard.FaceAuthUiEvent
27 import com.android.systemui.Dumpable
28 import com.android.systemui.R
29 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
30 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
31 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
32 import com.android.systemui.dagger.SysUISingleton
33 import com.android.systemui.dagger.qualifiers.Application
34 import com.android.systemui.dagger.qualifiers.Background
35 import com.android.systemui.dagger.qualifiers.Main
36 import com.android.systemui.dump.DumpManager
37 import com.android.systemui.flags.FeatureFlags
38 import com.android.systemui.flags.Flags
39 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
40 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
41 import com.android.systemui.keyguard.shared.model.AcquiredFaceAuthenticationStatus
42 import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
43 import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
44 import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
45 import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus
46 import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus
47 import com.android.systemui.keyguard.shared.model.KeyguardState
48 import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus
49 import com.android.systemui.keyguard.shared.model.TransitionState
50 import com.android.systemui.log.FaceAuthenticationLogger
51 import com.android.systemui.log.SessionTracker
52 import com.android.systemui.log.table.TableLogBuffer
53 import com.android.systemui.log.table.logDiffsForTable
54 import com.android.systemui.statusbar.phone.KeyguardBypassController
55 import com.android.systemui.user.data.repository.UserRepository
56 import java.io.PrintWriter
57 import java.util.Arrays
58 import java.util.stream.Collectors
59 import javax.inject.Inject
60 import kotlinx.coroutines.CoroutineDispatcher
61 import kotlinx.coroutines.CoroutineScope
62 import kotlinx.coroutines.ExperimentalCoroutinesApi
63 import kotlinx.coroutines.Job
64 import kotlinx.coroutines.channels.awaitClose
65 import kotlinx.coroutines.delay
66 import kotlinx.coroutines.flow.Flow
67 import kotlinx.coroutines.flow.MutableStateFlow
68 import kotlinx.coroutines.flow.StateFlow
69 import kotlinx.coroutines.flow.combine
70 import kotlinx.coroutines.flow.distinctUntilChanged
71 import kotlinx.coroutines.flow.filter
72 import kotlinx.coroutines.flow.filterNotNull
73 import kotlinx.coroutines.flow.flowOf
74 import kotlinx.coroutines.flow.launchIn
75 import kotlinx.coroutines.flow.map
76 import kotlinx.coroutines.flow.merge
77 import kotlinx.coroutines.flow.onEach
78 import kotlinx.coroutines.launch
79 import kotlinx.coroutines.withContext
80
81 /**
82 * API to run face authentication and detection for device entry / on keyguard (as opposed to the
83 * biometric prompt).
84 */
85 interface DeviceEntryFaceAuthRepository {
86 /** Provide the current face authentication state for device entry. */
87 val isAuthenticated: Flow<Boolean>
88
89 /** Whether face auth can run at this point. */
90 val canRunFaceAuth: StateFlow<Boolean>
91
92 /** Provide the current status of face authentication. */
93 val authenticationStatus: Flow<FaceAuthenticationStatus>
94
95 /** Provide the current status of face detection. */
96 val detectionStatus: Flow<FaceDetectionStatus>
97
98 /** Current state of whether face authentication is locked out or not. */
99 val isLockedOut: StateFlow<Boolean>
100
101 /** Current state of whether face authentication is running. */
102 val isAuthRunning: StateFlow<Boolean>
103
104 /** Whether bypass is currently enabled */
105 val isBypassEnabled: Flow<Boolean>
106
107 /** Set whether face authentication should be locked out or not */
108 fun lockoutFaceAuth()
109
110 /**
111 * Cancel current face authentication and prevent it from running until [resumeFaceAuth] is
112 * invoked.
113 */
114 fun pauseFaceAuth()
115
116 /**
117 * Allow face auth paused using [pauseFaceAuth] to run again. The next invocation to
118 * [authenticate] will run as long as other gating conditions don't stop it from running.
119 */
120 fun resumeFaceAuth()
121
122 /**
123 * Trigger face authentication.
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 suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean = false)
132
133 /** Stop currently running face authentication or detection. */
134 fun cancel()
135 }
136
137 @OptIn(ExperimentalCoroutinesApi::class)
138 @SysUISingleton
139 class DeviceEntryFaceAuthRepositoryImpl
140 @Inject
141 constructor(
142 context: Context,
143 private val faceManager: FaceManager? = null,
144 private val userRepository: UserRepository,
145 private val keyguardBypassController: KeyguardBypassController? = null,
146 @Application private val applicationScope: CoroutineScope,
147 @Main private val mainDispatcher: CoroutineDispatcher,
148 @Background private val backgroundDispatcher: CoroutineDispatcher,
149 private val sessionTracker: SessionTracker,
150 private val uiEventsLogger: UiEventLogger,
151 private val faceAuthLogger: FaceAuthenticationLogger,
152 private val biometricSettingsRepository: BiometricSettingsRepository,
153 private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
154 private val trustRepository: TrustRepository,
155 private val keyguardRepository: KeyguardRepository,
156 private val keyguardInteractor: KeyguardInteractor,
157 private val alternateBouncerInteractor: AlternateBouncerInteractor,
158 @FaceDetectTableLog private val faceDetectLog: TableLogBuffer,
159 @FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
160 private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
161 private val featureFlags: FeatureFlags,
162 dumpManager: DumpManager,
163 ) : DeviceEntryFaceAuthRepository, Dumpable {
164 private var authCancellationSignal: CancellationSignal? = null
165 private var detectCancellationSignal: CancellationSignal? = null
166 private var faceAcquiredInfoIgnoreList: Set<Int>
167 private var retryCount = 0
168
169 private var cancelNotReceivedHandlerJob: Job? = null
170 private var halErrorRetryJob: Job? = null
171
172 private val _authenticationStatus: MutableStateFlow<FaceAuthenticationStatus?> =
173 MutableStateFlow(null)
174 override val authenticationStatus: Flow<FaceAuthenticationStatus>
175 get() = _authenticationStatus.filterNotNull()
176
177 private val _detectionStatus = MutableStateFlow<FaceDetectionStatus?>(null)
178 override val detectionStatus: Flow<FaceDetectionStatus>
179 get() = _detectionStatus.filterNotNull()
180
181 private val _isLockedOut = MutableStateFlow(false)
182 override val isLockedOut: StateFlow<Boolean> = _isLockedOut
183
184 val isDetectionSupported =
185 faceManager?.sensorPropertiesInternal?.firstOrNull()?.supportsFaceDetection ?: false
186
187 private val _isAuthRunning = MutableStateFlow(false)
188 override val isAuthRunning: StateFlow<Boolean>
189 get() = _isAuthRunning
190
191 private val faceAuthPaused = MutableStateFlow(false)
pauseFaceAuthnull192 override fun pauseFaceAuth() {
193 faceAuthPaused.value = true
194 }
195
resumeFaceAuthnull196 override fun resumeFaceAuth() {
197 faceAuthPaused.value = false
198 }
199
200 private val keyguardSessionId: InstanceId?
201 get() = sessionTracker.getSessionId(StatusBarManager.SESSION_KEYGUARD)
202
203 private val _canRunFaceAuth = MutableStateFlow(true)
204 override val canRunFaceAuth: StateFlow<Boolean>
205 get() = _canRunFaceAuth
206
207 private val canRunDetection = MutableStateFlow(false)
208
209 private val _isAuthenticated = MutableStateFlow(false)
210 override val isAuthenticated: Flow<Boolean>
211 get() = _isAuthenticated
212
213 override val isBypassEnabled: Flow<Boolean> =
<lambda>null214 keyguardBypassController?.let {
215 conflatedCallbackFlow {
216 val callback =
217 object : KeyguardBypassController.OnBypassStateChangedListener {
218 override fun onBypassStateChanged(isEnabled: Boolean) {
219 trySendWithFailureLogging(isEnabled, TAG, "BypassStateChanged")
220 }
221 }
222 it.registerOnBypassStateChangedListener(callback)
223 trySendWithFailureLogging(it.bypassEnabled, TAG, "BypassStateChanged")
224 awaitClose { it.unregisterOnBypassStateChangedListener(callback) }
225 }
226 }
227 ?: flowOf(false)
228
lockoutFaceAuthnull229 override fun lockoutFaceAuth() {
230 _isLockedOut.value = true
231 }
232
233 private val faceLockoutResetCallback =
234 object : FaceManager.LockoutResetCallback() {
onLockoutResetnull235 override fun onLockoutReset(sensorId: Int) {
236 _isLockedOut.value = false
237 }
238 }
239
240 init {
241 faceManager?.addLockoutResetCallback(faceLockoutResetCallback)
242 faceAcquiredInfoIgnoreList =
243 Arrays.stream(
244 context.resources.getIntArray(
245 R.array.config_face_acquire_device_entry_ignorelist
246 )
247 )
248 .boxed()
249 .collect(Collectors.toSet())
250 dumpManager.registerCriticalDumpable("DeviceEntryFaceAuthRepositoryImpl", this)
251
252 if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) {
253 observeFaceAuthGatingChecks()
254 observeFaceDetectGatingChecks()
255 observeFaceAuthResettingConditions()
256 listenForSchedulingWatchdog()
257 }
258 }
259
listenForSchedulingWatchdognull260 private fun listenForSchedulingWatchdog() {
261 keyguardTransitionInteractor.anyStateToGoneTransition
262 .filter { it.transitionState == TransitionState.FINISHED }
263 .onEach {
264 // We deliberately want to run this in background because scheduleWatchdog does
265 // a Binder IPC.
266 withContext(backgroundDispatcher) {
267 faceAuthLogger.watchdogScheduled()
268 faceManager?.scheduleWatchdog()
269 }
270 }
271 .launchIn(applicationScope)
272 }
273
observeFaceAuthResettingConditionsnull274 private fun observeFaceAuthResettingConditions() {
275 // Clear auth status when keyguard is going away or when the user is switching or device
276 // starts going to sleep.
277 merge(
278 keyguardRepository.wakefulness.map { it.isStartingToSleepOrAsleep() },
279 if (featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
280 keyguardTransitionInteractor.isInTransitionToState(KeyguardState.GONE)
281 } else {
282 keyguardRepository.isKeyguardGoingAway
283 },
284 userRepository.userSwitchingInProgress,
285 )
286 .onEach { anyOfThemIsTrue ->
287 if (anyOfThemIsTrue) {
288 _isAuthenticated.value = false
289 retryCount = 0
290 halErrorRetryJob?.cancel()
291 }
292 }
293 .launchIn(applicationScope)
294 }
295
observeFaceDetectGatingChecksnull296 private fun observeFaceDetectGatingChecks() {
297 // Face detection can run only when lockscreen bypass is enabled
298 // & detection is supported
299 // & biometric unlock is not allowed
300 // or user is trusted by trust manager & we want to run face detect to dismiss keyguard
301 listOf(
302 canFaceAuthOrDetectRun(faceDetectLog),
303 logAndObserve(isBypassEnabled, "isBypassEnabled", faceDetectLog),
304 logAndObserve(
305 biometricSettingsRepository.isFaceAuthCurrentlyAllowed
306 .isFalse()
307 .or(trustRepository.isCurrentUserTrusted),
308 "faceAuthIsNotCurrentlyAllowedOrCurrentUserIsTrusted",
309 faceDetectLog
310 ),
311 // We don't want to run face detect if fingerprint can be used to unlock the device
312 // but it's not possible to authenticate with FP from the bouncer (UDFPS)
313 logAndObserve(
314 and(isUdfps(), deviceEntryFingerprintAuthRepository.isRunning).isFalse(),
315 "udfpsAuthIsNotPossibleAnymore",
316 faceDetectLog
317 )
318 )
319 .reduce(::and)
320 .distinctUntilChanged()
321 .onEach {
322 faceAuthLogger.canRunDetectionChanged(it)
323 canRunDetection.value = it
324 if (!it) {
325 cancelDetection()
326 }
327 }
328 .logDiffsForTable(faceDetectLog, "", "canFaceDetectRun", false)
329 .launchIn(applicationScope)
330 }
331
isUdfpsnull332 private fun isUdfps() =
333 deviceEntryFingerprintAuthRepository.availableFpSensorType.map {
334 it == BiometricType.UNDER_DISPLAY_FINGERPRINT
335 }
336
canFaceAuthOrDetectRunnull337 private fun canFaceAuthOrDetectRun(tableLogBuffer: TableLogBuffer): Flow<Boolean> {
338 return listOf(
339 logAndObserve(
340 biometricSettingsRepository.isFaceAuthEnrolledAndEnabled,
341 "isFaceAuthEnrolledAndEnabled",
342 tableLogBuffer
343 ),
344 logAndObserve(faceAuthPaused.isFalse(), "faceAuthIsNotPaused", tableLogBuffer),
345 logAndObserve(
346 keyguardRepository.isKeyguardGoingAway.isFalse(),
347 "keyguardNotGoingAway",
348 tableLogBuffer
349 ),
350 logAndObserve(
351 keyguardRepository.wakefulness.map { it.isStartingToSleep() }.isFalse(),
352 "deviceNotStartingToSleep",
353 tableLogBuffer
354 ),
355 logAndObserve(
356 keyguardInteractor.isSecureCameraActive
357 .isFalse()
358 .or(
359 alternateBouncerInteractor.isVisible.or(
360 keyguardInteractor.primaryBouncerShowing
361 )
362 ),
363 "secureCameraNotActiveOrAnyBouncerIsShowing",
364 tableLogBuffer
365 ),
366 logAndObserve(
367 biometricSettingsRepository.isFaceAuthSupportedInCurrentPosture,
368 "isFaceAuthSupportedInCurrentPosture",
369 tableLogBuffer
370 ),
371 logAndObserve(
372 biometricSettingsRepository.isCurrentUserInLockdown.isFalse(),
373 "userHasNotLockedDownDevice",
374 tableLogBuffer
375 ),
376 logAndObserve(
377 keyguardRepository.isKeyguardShowing,
378 "isKeyguardShowing",
379 tableLogBuffer
380 )
381 )
382 .reduce(::and)
383 }
384
observeFaceAuthGatingChecksnull385 private fun observeFaceAuthGatingChecks() {
386 // Face auth can run only if all of the gating conditions are true.
387 listOf(
388 canFaceAuthOrDetectRun(faceAuthLog),
389 logAndObserve(isLockedOut.isFalse(), "isNotInLockOutState", faceAuthLog),
390 logAndObserve(
391 trustRepository.isCurrentUserTrusted.isFalse(),
392 "currentUserIsNotTrusted",
393 faceAuthLog
394 ),
395 logAndObserve(
396 biometricSettingsRepository.isFaceAuthCurrentlyAllowed,
397 "isFaceAuthCurrentlyAllowed",
398 faceAuthLog
399 ),
400 logAndObserve(isAuthenticated.isFalse(), "faceNotAuthenticated", faceAuthLog),
401 )
402 .reduce(::and)
403 .distinctUntilChanged()
404 .onEach {
405 faceAuthLogger.canFaceAuthRunChanged(it)
406 _canRunFaceAuth.value = it
407 if (!it) {
408 // Cancel currently running auth if any of the gating checks are false.
409 faceAuthLogger.cancellingFaceAuth()
410 cancel()
411 }
412 }
413 .logDiffsForTable(faceAuthLog, "", "canFaceAuthRun", false)
414 .launchIn(applicationScope)
415 }
416
417 private val faceAuthCallback =
418 object : FaceManager.AuthenticationCallback() {
onAuthenticationFailednull419 override fun onAuthenticationFailed() {
420 _authenticationStatus.value = FailedFaceAuthenticationStatus()
421 _isAuthenticated.value = false
422 faceAuthLogger.authenticationFailed()
423 onFaceAuthRequestCompleted()
424 }
425
onAuthenticationAcquirednull426 override fun onAuthenticationAcquired(acquireInfo: Int) {
427 _authenticationStatus.value = AcquiredFaceAuthenticationStatus(acquireInfo)
428 }
429
onAuthenticationErrornull430 override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
431 val errorStatus = ErrorFaceAuthenticationStatus(errorCode, errString.toString())
432 if (errorStatus.isLockoutError()) {
433 _isLockedOut.value = true
434 }
435 _authenticationStatus.value = errorStatus
436 _isAuthenticated.value = false
437 if (errorStatus.isCancellationError()) {
438 handleFaceCancellationError()
439 }
440 if (errorStatus.isHardwareError()) {
441 faceAuthLogger.hardwareError(errorStatus)
442 handleFaceHardwareError()
443 }
444 faceAuthLogger.authenticationError(
445 errorCode,
446 errString,
447 errorStatus.isLockoutError(),
448 errorStatus.isCancellationError()
449 )
450 onFaceAuthRequestCompleted()
451 }
452
onAuthenticationHelpnull453 override fun onAuthenticationHelp(code: Int, helpStr: CharSequence?) {
454 if (faceAcquiredInfoIgnoreList.contains(code)) {
455 return
456 }
457 _authenticationStatus.value = HelpFaceAuthenticationStatus(code, helpStr.toString())
458 }
459
onAuthenticationSucceedednull460 override fun onAuthenticationSucceeded(result: FaceManager.AuthenticationResult) {
461 _authenticationStatus.value = SuccessFaceAuthenticationStatus(result)
462 _isAuthenticated.value = true
463 faceAuthLogger.faceAuthSuccess(result)
464 onFaceAuthRequestCompleted()
465 }
466 }
467
handleFaceCancellationErrornull468 private fun handleFaceCancellationError() {
469 applicationScope.launch {
470 faceAuthRequestedWhileCancellation?.let {
471 faceAuthLogger.launchingQueuedFaceAuthRequest(it)
472 authenticate(it)
473 }
474 faceAuthRequestedWhileCancellation = null
475 }
476 }
477
handleFaceHardwareErrornull478 private fun handleFaceHardwareError() {
479 if (retryCount < HAL_ERROR_RETRY_MAX) {
480 retryCount++
481 halErrorRetryJob?.cancel()
482 halErrorRetryJob =
483 applicationScope.launch {
484 delay(HAL_ERROR_RETRY_TIMEOUT)
485 if (retryCount < HAL_ERROR_RETRY_MAX) {
486 faceAuthLogger.attemptingRetryAfterHardwareError(retryCount)
487 authenticate(
488 FaceAuthUiEvent.FACE_AUTH_TRIGGERED_RETRY_AFTER_HW_UNAVAILABLE,
489 fallbackToDetection = false
490 )
491 }
492 }
493 }
494 }
495
onFaceAuthRequestCompletednull496 private fun onFaceAuthRequestCompleted() {
497 cancelNotReceivedHandlerJob?.cancel()
498 cancellationInProgress = false
499 _isAuthRunning.value = false
500 authCancellationSignal = null
501 }
502
503 private val detectionCallback =
isStrongnull504 FaceManager.FaceDetectionCallback { sensorId, userId, isStrong ->
505 faceAuthLogger.faceDetected()
506 _detectionStatus.value = FaceDetectionStatus(sensorId, userId, isStrong)
507 }
508
509 private var cancellationInProgress = false
510 private var faceAuthRequestedWhileCancellation: FaceAuthUiEvent? = null
511
authenticatenull512 override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
513 if (_isAuthRunning.value) {
514 faceAuthLogger.ignoredFaceAuthTrigger(uiEvent, "face auth is currently running")
515 return
516 }
517
518 if (cancellationInProgress) {
519 faceAuthLogger.queuingRequestWhileCancelling(
520 faceAuthRequestedWhileCancellation,
521 uiEvent
522 )
523 faceAuthRequestedWhileCancellation = uiEvent
524 return
525 } else {
526 faceAuthRequestedWhileCancellation = null
527 }
528
529 if (canRunFaceAuth.value) {
530 withContext(mainDispatcher) {
531 // We always want to invoke face auth in the main thread.
532 authCancellationSignal = CancellationSignal()
533 _isAuthRunning.value = true
534 uiEventsLogger.logWithInstanceIdAndPosition(
535 uiEvent,
536 0,
537 null,
538 keyguardSessionId,
539 uiEvent.extraInfo
540 )
541 faceAuthLogger.authenticating(uiEvent)
542 faceManager?.authenticate(
543 null,
544 authCancellationSignal,
545 faceAuthCallback,
546 null,
547 FaceAuthenticateOptions.Builder().setUserId(currentUserId).build()
548 )
549 }
550 } else if (fallbackToDetection && canRunDetection.value) {
551 faceAuthLogger.ignoredFaceAuthTrigger(
552 uiEvent,
553 "face auth gating check is false, falling back to detection."
554 )
555 detect()
556 } else {
557 faceAuthLogger.ignoredFaceAuthTrigger(
558 uiEvent,
559 "face auth & detect gating check is false"
560 )
561 }
562 }
563
detectnull564 suspend fun detect() {
565 if (!isDetectionSupported) {
566 faceAuthLogger.detectionNotSupported(faceManager, faceManager?.sensorPropertiesInternal)
567 return
568 }
569 if (_isAuthRunning.value) {
570 faceAuthLogger.skippingDetection(_isAuthRunning.value, detectCancellationSignal != null)
571 return
572 }
573 detectCancellationSignal?.cancel()
574 detectCancellationSignal = CancellationSignal()
575 withContext(mainDispatcher) {
576 // We always want to invoke face detect in the main thread.
577 faceAuthLogger.faceDetectionStarted()
578 faceManager?.detectFace(
579 checkNotNull(detectCancellationSignal),
580 detectionCallback,
581 FaceAuthenticateOptions.Builder().setUserId(currentUserId).build()
582 )
583 }
584 }
585
586 private val currentUserId: Int
587 get() = userRepository.getSelectedUserInfo().id
588
cancelDetectionnull589 private fun cancelDetection() {
590 detectCancellationSignal?.cancel()
591 detectCancellationSignal = null
592 }
593
cancelnull594 override fun cancel() {
595 if (authCancellationSignal == null) return
596
597 authCancellationSignal?.cancel()
598 cancelNotReceivedHandlerJob?.cancel()
599 cancelNotReceivedHandlerJob =
600 applicationScope.launch {
601 delay(DEFAULT_CANCEL_SIGNAL_TIMEOUT)
602 faceAuthLogger.cancelSignalNotReceived(
603 _isAuthRunning.value,
604 _isLockedOut.value,
605 cancellationInProgress,
606 faceAuthRequestedWhileCancellation
607 )
608 _authenticationStatus.value = ErrorFaceAuthenticationStatus.cancelNotReceivedError()
609 onFaceAuthRequestCompleted()
610 }
611 cancellationInProgress = true
612 _isAuthRunning.value = false
613 }
614
logAndObservenull615 private fun logAndObserve(
616 cond: Flow<Boolean>,
617 conditionName: String,
618 logBuffer: TableLogBuffer
619 ): Flow<Boolean> {
620 return cond
621 .distinctUntilChanged()
622 .logDiffsForTable(
623 logBuffer,
624 columnName = conditionName,
625 columnPrefix = "",
626 initialValue = false
627 )
628 .onEach { faceAuthLogger.observedConditionChanged(it, conditionName) }
629 }
630
631 companion object {
632 const val TAG = "DeviceEntryFaceAuthRepository"
633
634 /**
635 * If no cancel signal has been received after this amount of time, assume that it is
636 * cancelled.
637 */
638 const val DEFAULT_CANCEL_SIGNAL_TIMEOUT = 3000L
639
640 /** Number of allowed retries whenever there is a face hardware error */
641 const val HAL_ERROR_RETRY_MAX = 5
642
643 /** Timeout before retries whenever there is a HAL error. */
644 const val HAL_ERROR_RETRY_TIMEOUT = 500L // ms
645 }
646
dumpnull647 override fun dump(pw: PrintWriter, args: Array<out String>) {
648 pw.println("DeviceEntryFaceAuthRepositoryImpl state:")
649 pw.println(" cancellationInProgress: $cancellationInProgress")
650 pw.println(" _isLockedOut.value: ${_isLockedOut.value}")
651 pw.println(" _isAuthRunning.value: ${_isAuthRunning.value}")
652 pw.println(" isDetectionSupported: $isDetectionSupported")
653 pw.println(" FaceManager state:")
654 pw.println(" faceManager: $faceManager")
655 pw.println(" sensorPropertiesInternal: ${faceManager?.sensorPropertiesInternal}")
656 pw.println(
657 " supportsFaceDetection: " +
658 "${faceManager?.sensorPropertiesInternal?.firstOrNull()?.supportsFaceDetection}"
659 )
660 pw.println(
661 " faceAuthRequestedWhileCancellation: ${faceAuthRequestedWhileCancellation?.reason}"
662 )
663 pw.println(" authCancellationSignal: $authCancellationSignal")
664 pw.println(" detectCancellationSignal: $detectCancellationSignal")
665 pw.println(" faceAcquiredInfoIgnoreList: $faceAcquiredInfoIgnoreList")
666 pw.println(" _authenticationStatus: ${_authenticationStatus.value}")
667 pw.println(" _detectionStatus: ${_detectionStatus.value}")
668 pw.println(" currentUserId: $currentUserId")
669 pw.println(" keyguardSessionId: $keyguardSessionId")
670 pw.println(" lockscreenBypassEnabled: ${keyguardBypassController?.bypassEnabled ?: false}")
671 }
672 }
673 /** Combine two boolean flows by and-ing both of them */
andnull674 private fun and(flow: Flow<Boolean>, anotherFlow: Flow<Boolean>) =
675 flow.combine(anotherFlow) { a, b -> a && b }
676
677 /** Combine two boolean flows by or-ing both of them */
ornull678 private fun Flow<Boolean>.or(anotherFlow: Flow<Boolean>) =
679 this.combine(anotherFlow) { a, b -> a || b }
680
681 /** "Not" the given flow. The return [Flow] will be true when [this] flow is false. */
isFalsenull682 private fun Flow<Boolean>.isFalse(): Flow<Boolean> {
683 return this.map { !it }
684 }
685