• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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