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