• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.deviceentry.data.repository
18 
19 import android.app.StatusBarManager
20 import android.content.Context
21 import android.hardware.face.FaceManager
22 import android.os.CancellationSignal
23 import com.android.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