1 /*
2  * 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)
192     override fun pauseFaceAuth() {
193         faceAuthPaused.value = true
194     }
195 
196     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> =
214         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 
229     override fun lockoutFaceAuth() {
230         _isLockedOut.value = true
231     }
232 
233     private val faceLockoutResetCallback =
234         object : FaceManager.LockoutResetCallback() {
235             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 
260     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 
274     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 
296     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 
332     private fun isUdfps() =
333         deviceEntryFingerprintAuthRepository.availableFpSensorType.map {
334             it == BiometricType.UNDER_DISPLAY_FINGERPRINT
335         }
336 
337     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 
385     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() {
419             override fun onAuthenticationFailed() {
420                 _authenticationStatus.value = FailedFaceAuthenticationStatus()
421                 _isAuthenticated.value = false
422                 faceAuthLogger.authenticationFailed()
423                 onFaceAuthRequestCompleted()
424             }
425 
426             override fun onAuthenticationAcquired(acquireInfo: Int) {
427                 _authenticationStatus.value = AcquiredFaceAuthenticationStatus(acquireInfo)
428             }
429 
430             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 
453             override fun onAuthenticationHelp(code: Int, helpStr: CharSequence?) {
454                 if (faceAcquiredInfoIgnoreList.contains(code)) {
455                     return
456                 }
457                 _authenticationStatus.value = HelpFaceAuthenticationStatus(code, helpStr.toString())
458             }
459 
460             override fun onAuthenticationSucceeded(result: FaceManager.AuthenticationResult) {
461                 _authenticationStatus.value = SuccessFaceAuthenticationStatus(result)
462                 _isAuthenticated.value = true
463                 faceAuthLogger.faceAuthSuccess(result)
464                 onFaceAuthRequestCompleted()
465             }
466         }
467 
468     private fun handleFaceCancellationError() {
469         applicationScope.launch {
470             faceAuthRequestedWhileCancellation?.let {
471                 faceAuthLogger.launchingQueuedFaceAuthRequest(it)
472                 authenticate(it)
473             }
474             faceAuthRequestedWhileCancellation = null
475         }
476     }
477 
478     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 
496     private fun onFaceAuthRequestCompleted() {
497         cancelNotReceivedHandlerJob?.cancel()
498         cancellationInProgress = false
499         _isAuthRunning.value = false
500         authCancellationSignal = null
501     }
502 
503     private val detectionCallback =
504         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 
512     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 
564     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 
589     private fun cancelDetection() {
590         detectCancellationSignal?.cancel()
591         detectCancellationSignal = null
592     }
593 
594     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 
615     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 
647     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 */
674 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 */
678 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. */
682 private fun Flow<Boolean>.isFalse(): Flow<Boolean> {
683     return this.map { !it }
684 }
685