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.FaceManager 22 import android.os.CancellationSignal 23 import com.android.internal.logging.InstanceId 24 import com.android.internal.logging.UiEventLogger 25 import com.android.keyguard.FaceAuthUiEvent 26 import com.android.systemui.Dumpable 27 import com.android.systemui.R 28 import com.android.systemui.dagger.SysUISingleton 29 import com.android.systemui.dagger.qualifiers.Application 30 import com.android.systemui.dagger.qualifiers.Main 31 import com.android.systemui.dump.DumpManager 32 import com.android.systemui.keyguard.shared.model.AcquiredAuthenticationStatus 33 import com.android.systemui.keyguard.shared.model.AuthenticationStatus 34 import com.android.systemui.keyguard.shared.model.DetectionStatus 35 import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus 36 import com.android.systemui.keyguard.shared.model.FailedAuthenticationStatus 37 import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus 38 import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus 39 import com.android.systemui.log.FaceAuthenticationLogger 40 import com.android.systemui.log.SessionTracker 41 import com.android.systemui.statusbar.phone.KeyguardBypassController 42 import com.android.systemui.user.data.repository.UserRepository 43 import java.io.PrintWriter 44 import java.util.Arrays 45 import java.util.stream.Collectors 46 import javax.inject.Inject 47 import kotlinx.coroutines.CoroutineDispatcher 48 import kotlinx.coroutines.CoroutineScope 49 import kotlinx.coroutines.Job 50 import kotlinx.coroutines.delay 51 import kotlinx.coroutines.flow.Flow 52 import kotlinx.coroutines.flow.MutableStateFlow 53 import kotlinx.coroutines.flow.filterNotNull 54 import kotlinx.coroutines.launch 55 import kotlinx.coroutines.withContext 56 57 /** 58 * API to run face authentication and detection for device entry / on keyguard (as opposed to the 59 * biometric prompt). 60 */ 61 interface KeyguardFaceAuthManager { 62 /** 63 * Trigger face authentication. 64 * 65 * [uiEvent] provided should be logged whenever face authentication runs. Invocation should be 66 * ignored if face authentication is already running. Results should be propagated through 67 * [authenticationStatus] 68 */ 69 suspend fun authenticate(uiEvent: FaceAuthUiEvent) 70 71 /** 72 * Trigger face detection. 73 * 74 * Invocation should be ignored if face authentication is currently running. 75 */ 76 suspend fun detect() 77 78 /** Stop currently running face authentication or detection. */ 79 fun cancel() 80 81 /** Provide the current status of face authentication. */ 82 val authenticationStatus: Flow<AuthenticationStatus> 83 84 /** Provide the current status of face detection. */ 85 val detectionStatus: Flow<DetectionStatus> 86 87 /** Current state of whether face authentication is locked out or not. */ 88 val isLockedOut: Flow<Boolean> 89 90 /** Current state of whether face authentication is running. */ 91 val isAuthRunning: Flow<Boolean> 92 93 /** Is face detection supported. */ 94 val isDetectionSupported: Boolean 95 } 96 97 @SysUISingleton 98 class KeyguardFaceAuthManagerImpl 99 @Inject 100 constructor( 101 context: Context, 102 private val faceManager: FaceManager? = null, 103 private val userRepository: UserRepository, 104 private val keyguardBypassController: KeyguardBypassController? = null, 105 @Application private val applicationScope: CoroutineScope, 106 @Main private val mainDispatcher: CoroutineDispatcher, 107 private val sessionTracker: SessionTracker, 108 private val uiEventsLogger: UiEventLogger, 109 private val faceAuthLogger: FaceAuthenticationLogger, 110 dumpManager: DumpManager, 111 ) : KeyguardFaceAuthManager, Dumpable { 112 private var cancellationSignal: CancellationSignal? = null 113 private val lockscreenBypassEnabled: Boolean 114 get() = keyguardBypassController?.bypassEnabled ?: false 115 private var faceAcquiredInfoIgnoreList: Set<Int> 116 117 private val faceLockoutResetCallback = 118 object : FaceManager.LockoutResetCallback() { onLockoutResetnull119 override fun onLockoutReset(sensorId: Int) { 120 _isLockedOut.value = false 121 } 122 } 123 124 init { 125 faceManager?.addLockoutResetCallback(faceLockoutResetCallback) 126 faceAcquiredInfoIgnoreList = 127 Arrays.stream( 128 context.resources.getIntArray( 129 R.array.config_face_acquire_device_entry_ignorelist 130 ) 131 ) 132 .boxed() 133 .collect(Collectors.toSet()) 134 dumpManager.registerCriticalDumpable("KeyguardFaceAuthManagerImpl", this) 135 } 136 137 private val faceAuthCallback = 138 object : FaceManager.AuthenticationCallback() { onAuthenticationFailednull139 override fun onAuthenticationFailed() { 140 _authenticationStatus.value = FailedAuthenticationStatus 141 faceAuthLogger.authenticationFailed() 142 onFaceAuthRequestCompleted() 143 } 144 onAuthenticationAcquirednull145 override fun onAuthenticationAcquired(acquireInfo: Int) { 146 _authenticationStatus.value = AcquiredAuthenticationStatus(acquireInfo) 147 faceAuthLogger.authenticationAcquired(acquireInfo) 148 } 149 onAuthenticationErrornull150 override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) { 151 val errorStatus = ErrorAuthenticationStatus(errorCode, errString.toString()) 152 if (errorStatus.isLockoutError()) { 153 _isLockedOut.value = true 154 } 155 _authenticationStatus.value = errorStatus 156 if (errorStatus.isCancellationError()) { 157 cancelNotReceivedHandlerJob?.cancel() 158 applicationScope.launch { 159 faceAuthLogger.launchingQueuedFaceAuthRequest( 160 faceAuthRequestedWhileCancellation 161 ) 162 faceAuthRequestedWhileCancellation?.let { authenticate(it) } 163 faceAuthRequestedWhileCancellation = null 164 } 165 } 166 faceAuthLogger.authenticationError( 167 errorCode, 168 errString, 169 errorStatus.isLockoutError(), 170 errorStatus.isCancellationError() 171 ) 172 onFaceAuthRequestCompleted() 173 } 174 onAuthenticationHelpnull175 override fun onAuthenticationHelp(code: Int, helpStr: CharSequence?) { 176 if (faceAcquiredInfoIgnoreList.contains(code)) { 177 return 178 } 179 _authenticationStatus.value = HelpAuthenticationStatus(code, helpStr.toString()) 180 } 181 onAuthenticationSucceedednull182 override fun onAuthenticationSucceeded(result: FaceManager.AuthenticationResult) { 183 _authenticationStatus.value = SuccessAuthenticationStatus(result) 184 faceAuthLogger.faceAuthSuccess(result) 185 onFaceAuthRequestCompleted() 186 } 187 } 188 onFaceAuthRequestCompletednull189 private fun onFaceAuthRequestCompleted() { 190 cancellationInProgress = false 191 _isAuthRunning.value = false 192 cancellationSignal = null 193 } 194 195 private val detectionCallback = isStrongnull196 FaceManager.FaceDetectionCallback { sensorId, userId, isStrong -> 197 faceAuthLogger.faceDetected() 198 _detectionStatus.value = DetectionStatus(sensorId, userId, isStrong) 199 } 200 201 private var cancellationInProgress = false 202 private var faceAuthRequestedWhileCancellation: FaceAuthUiEvent? = null 203 authenticatenull204 override suspend fun authenticate(uiEvent: FaceAuthUiEvent) { 205 if (_isAuthRunning.value) { 206 faceAuthLogger.ignoredFaceAuthTrigger(uiEvent) 207 return 208 } 209 210 if (cancellationInProgress) { 211 faceAuthLogger.queuingRequestWhileCancelling( 212 faceAuthRequestedWhileCancellation, 213 uiEvent 214 ) 215 faceAuthRequestedWhileCancellation = uiEvent 216 return 217 } else { 218 faceAuthRequestedWhileCancellation = null 219 } 220 221 withContext(mainDispatcher) { 222 // We always want to invoke face auth in the main thread. 223 cancellationSignal = CancellationSignal() 224 _isAuthRunning.value = true 225 uiEventsLogger.logWithInstanceIdAndPosition( 226 uiEvent, 227 0, 228 null, 229 keyguardSessionId, 230 uiEvent.extraInfo 231 ) 232 faceAuthLogger.authenticating(uiEvent) 233 faceManager?.authenticate( 234 null, 235 cancellationSignal, 236 faceAuthCallback, 237 null, 238 currentUserId, 239 lockscreenBypassEnabled 240 ) 241 } 242 } 243 detectnull244 override suspend fun detect() { 245 if (!isDetectionSupported) { 246 faceAuthLogger.detectionNotSupported(faceManager, faceManager?.sensorPropertiesInternal) 247 return 248 } 249 if (_isAuthRunning.value) { 250 faceAuthLogger.skippingBecauseAlreadyRunning("detection") 251 return 252 } 253 254 cancellationSignal = CancellationSignal() 255 withContext(mainDispatcher) { 256 // We always want to invoke face detect in the main thread. 257 faceAuthLogger.faceDetectionStarted() 258 faceManager?.detectFace(cancellationSignal, detectionCallback, currentUserId) 259 } 260 } 261 262 private val currentUserId: Int 263 get() = userRepository.getSelectedUserInfo().id 264 cancelnull265 override fun cancel() { 266 if (cancellationSignal == null) return 267 268 cancellationSignal?.cancel() 269 cancelNotReceivedHandlerJob = 270 applicationScope.launch { 271 delay(DEFAULT_CANCEL_SIGNAL_TIMEOUT) 272 faceAuthLogger.cancelSignalNotReceived( 273 _isAuthRunning.value, 274 _isLockedOut.value, 275 cancellationInProgress, 276 faceAuthRequestedWhileCancellation 277 ) 278 onFaceAuthRequestCompleted() 279 } 280 cancellationInProgress = true 281 _isAuthRunning.value = false 282 } 283 284 private var cancelNotReceivedHandlerJob: Job? = null 285 286 private val _authenticationStatus: MutableStateFlow<AuthenticationStatus?> = 287 MutableStateFlow(null) 288 override val authenticationStatus: Flow<AuthenticationStatus> 289 get() = _authenticationStatus.filterNotNull() 290 291 private val _detectionStatus = MutableStateFlow<DetectionStatus?>(null) 292 override val detectionStatus: Flow<DetectionStatus> 293 get() = _detectionStatus.filterNotNull() 294 295 private val _isLockedOut = MutableStateFlow(false) 296 override val isLockedOut: Flow<Boolean> = _isLockedOut 297 298 override val isDetectionSupported = 299 faceManager?.sensorPropertiesInternal?.firstOrNull()?.supportsFaceDetection ?: false 300 301 private val _isAuthRunning = MutableStateFlow(false) 302 override val isAuthRunning: Flow<Boolean> 303 get() = _isAuthRunning 304 305 private val keyguardSessionId: InstanceId? 306 get() = sessionTracker.getSessionId(StatusBarManager.SESSION_KEYGUARD) 307 308 companion object { 309 const val TAG = "KeyguardFaceAuthManager" 310 311 /** 312 * If no cancel signal has been received after this amount of time, assume that it is 313 * cancelled. 314 */ 315 const val DEFAULT_CANCEL_SIGNAL_TIMEOUT = 3000L 316 } 317 dumpnull318 override fun dump(pw: PrintWriter, args: Array<out String>) { 319 pw.println("KeyguardFaceAuthManagerImpl state:") 320 pw.println(" cancellationInProgress: $cancellationInProgress") 321 pw.println(" _isLockedOut.value: ${_isLockedOut.value}") 322 pw.println(" _isAuthRunning.value: ${_isAuthRunning.value}") 323 pw.println(" isDetectionSupported: $isDetectionSupported") 324 pw.println(" FaceManager state:") 325 pw.println(" faceManager: $faceManager") 326 pw.println(" sensorPropertiesInternal: ${faceManager?.sensorPropertiesInternal}") 327 pw.println( 328 " supportsFaceDetection: " + 329 "${faceManager?.sensorPropertiesInternal?.firstOrNull()?.supportsFaceDetection}" 330 ) 331 pw.println( 332 " faceAuthRequestedWhileCancellation: ${faceAuthRequestedWhileCancellation?.reason}" 333 ) 334 pw.println(" cancellationSignal: $cancellationSignal") 335 pw.println(" faceAcquiredInfoIgnoreList: $faceAcquiredInfoIgnoreList") 336 pw.println(" _authenticationStatus: ${_authenticationStatus.value}") 337 pw.println(" _detectionStatus: ${_detectionStatus.value}") 338 pw.println(" currentUserId: $currentUserId") 339 pw.println(" keyguardSessionId: $keyguardSessionId") 340 pw.println(" lockscreenBypassEnabled: $lockscreenBypassEnabled") 341 } 342 } 343