1 /*
<lambda>null2 * Copyright (C) 2022 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.admin.DevicePolicyManager
20 import android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED
21 import android.content.Context
22 import android.content.IntentFilter
23 import android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE
24 import android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT
25 import android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE
26 import android.hardware.biometrics.BiometricManager
27 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback
28 import android.os.UserHandle
29 import android.util.Log
30 import com.android.internal.widget.LockPatternUtils
31 import com.android.systemui.Dumpable
32 import com.android.systemui.biometrics.AuthController
33 import com.android.systemui.biometrics.data.repository.FacePropertyRepository
34 import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
35 import com.android.systemui.biometrics.shared.model.SensorStrength
36 import com.android.systemui.broadcast.BroadcastDispatcher
37 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
38 import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
39 import com.android.systemui.dagger.SysUISingleton
40 import com.android.systemui.dagger.qualifiers.Application
41 import com.android.systemui.dagger.qualifiers.Background
42 import com.android.systemui.dump.DumpManager
43 import com.android.systemui.keyguard.shared.model.AuthenticationFlags
44 import com.android.systemui.keyguard.shared.model.DevicePosture
45 import com.android.systemui.res.R
46 import com.android.systemui.shade.ShadeDisplayAware
47 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
48 import com.android.systemui.user.data.repository.UserRepository
49 import java.io.PrintWriter
50 import javax.inject.Inject
51 import kotlinx.coroutines.CoroutineDispatcher
52 import kotlinx.coroutines.CoroutineScope
53 import kotlinx.coroutines.channels.awaitClose
54 import kotlinx.coroutines.flow.Flow
55 import kotlinx.coroutines.flow.MutableStateFlow
56 import kotlinx.coroutines.flow.SharingStarted
57 import kotlinx.coroutines.flow.StateFlow
58 import kotlinx.coroutines.flow.callbackFlow
59 import kotlinx.coroutines.flow.combine
60 import kotlinx.coroutines.flow.distinctUntilChanged
61 import kotlinx.coroutines.flow.filter
62 import kotlinx.coroutines.flow.flatMapLatest
63 import kotlinx.coroutines.flow.flowOf
64 import kotlinx.coroutines.flow.flowOn
65 import kotlinx.coroutines.flow.map
66 import kotlinx.coroutines.flow.onEach
67 import kotlinx.coroutines.flow.onStart
68 import kotlinx.coroutines.flow.stateIn
69 import kotlinx.coroutines.flow.transformLatest
70
71 /**
72 * Acts as source of truth for biometric authentication related settings like enrollments, device
73 * policy specifically for device entry usage.
74 *
75 * Abstracts-away data sources and their schemas so the rest of the app doesn't need to worry about
76 * upstream changes.
77 */
78 interface BiometricSettingsRepository {
79 /**
80 * If the current user can enter the device using fingerprint. This is true if user has enrolled
81 * fingerprints and fingerprint auth is not disabled for device entry through settings and
82 * device policy
83 */
84 val isFingerprintEnrolledAndEnabled: StateFlow<Boolean>
85
86 /**
87 * If the current user can enter the device using fingerprint, right now.
88 *
89 * This returns true if there are no strong auth flags that restrict the user from using
90 * fingerprint and [isFingerprintEnrolledAndEnabled] is true
91 */
92 val isFingerprintAuthCurrentlyAllowed: StateFlow<Boolean>
93
94 /**
95 * If the current user can use face auth to enter the device. This is true when the user has
96 * face auth enrolled, and is enabled in settings/device policy.
97 */
98 val isFaceAuthEnrolledAndEnabled: StateFlow<Boolean>
99
100 /**
101 * If the current user can use face auth to enter the device right now. This is true when
102 * [isFaceAuthEnrolledAndEnabled] is true and strong auth settings allow face auth to run and
103 * face auth is supported by the current device posture.
104 */
105 val isFaceAuthCurrentlyAllowed: Flow<Boolean>
106
107 /**
108 * Whether face authentication is supported for the current device posture. Face auth can be
109 * restricted to specific postures using [R.integer.config_face_auth_supported_posture]
110 */
111 val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>
112
113 /**
114 * Whether the user manually locked down the device. This doesn't include device policy manager
115 * lockdown.
116 */
117 val isCurrentUserInLockdown: Flow<Boolean>
118
119 /** Authentication flags set for the current user. */
120 val authenticationFlags: Flow<AuthenticationFlags>
121 }
122
123 private const val TAG = "BiometricsRepositoryImpl"
124
125 @SysUISingleton
126 class BiometricSettingsRepositoryImpl
127 @Inject
128 constructor(
129 @ShadeDisplayAware context: Context,
130 lockPatternUtils: LockPatternUtils,
131 broadcastDispatcher: BroadcastDispatcher,
132 authController: AuthController,
133 private val userRepository: UserRepository,
134 devicePolicyManager: DevicePolicyManager,
135 @Application scope: CoroutineScope,
136 @Background backgroundDispatcher: CoroutineDispatcher,
137 biometricManager: BiometricManager?,
138 devicePostureRepository: DevicePostureRepository,
139 facePropertyRepository: FacePropertyRepository,
140 fingerprintPropertyRepository: FingerprintPropertyRepository,
141 mobileConnectionsRepository: MobileConnectionsRepository,
142 dumpManager: DumpManager,
143 ) : BiometricSettingsRepository, Dumpable {
144
145 private val biometricsEnabledForUser = mutableMapOf<Int, Boolean>()
146 private val fingerprintEnabledForUser = mutableMapOf<Int, Boolean>()
147 private val faceEnabledForUser = mutableMapOf<Int, Boolean>()
148
149 override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>
150
151 private val strongAuthTracker = StrongAuthTracker(userRepository, context)
152
153 override val isCurrentUserInLockdown: Flow<Boolean> =
<lambda>null154 strongAuthTracker.currentUserAuthFlags.map { it.isInUserLockdown }
155
156 override val authenticationFlags: Flow<AuthenticationFlags> =
157 strongAuthTracker.currentUserAuthFlags
158
159 init {
160 Log.d(TAG, "Registering StrongAuthTracker")
161 lockPatternUtils.registerStrongAuthTracker(strongAuthTracker)
162 dumpManager.registerDumpable(this)
163 val configFaceAuthSupportedPosture =
164 DevicePosture.toPosture(
165 context.resources.getInteger(R.integer.config_face_auth_supported_posture)
166 )
167 isFaceAuthSupportedInCurrentPosture =
168 if (configFaceAuthSupportedPosture == DevicePosture.UNKNOWN) {
169 flowOf(true)
170 } else {
<lambda>null171 devicePostureRepository.currentDevicePosture.map {
172 it == configFaceAuthSupportedPosture
173 }
174 }
<lambda>null175 .onEach { Log.d(TAG, "isFaceAuthSupportedInCurrentPosture value changed to: $it") }
176 }
177
dumpnull178 override fun dump(pw: PrintWriter, args: Array<String?>) {
179 pw.println("isFingerprintEnrolledAndEnabled=${isFingerprintEnrolledAndEnabled.value}")
180 pw.println("isFingerprintAuthCurrentlyAllowed=${isFingerprintAuthCurrentlyAllowed.value}")
181 pw.println("isNonStrongBiometricAllowed=${isNonStrongBiometricAllowed.value}")
182 pw.println("isStrongBiometricAllowed=${isStrongBiometricAllowed.value}")
183 }
184
185 /** UserId of the current selected user. */
186 private val selectedUserId: Flow<Int> =
<lambda>null187 userRepository.selectedUserInfo.map { it.id }.distinctUntilChanged()
188
189 private val devicePolicyChangedForAllUsers =
190 broadcastDispatcher.broadcastFlow(
191 filter = IntentFilter(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
192 user = UserHandle.ALL
193 )
194
195 private val isFingerprintEnrolled: Flow<Boolean> =
currentUserIdnull196 selectedUserId.flatMapLatest { currentUserId ->
197 conflatedCallbackFlow {
198 val callback =
199 object : AuthController.Callback {
200 override fun onEnrollmentsChanged(
201 sensorBiometricType: BiometricType,
202 userId: Int,
203 hasEnrollments: Boolean
204 ) {
205 if (sensorBiometricType.isFingerprint && userId == currentUserId) {
206 trySendWithFailureLogging(
207 hasEnrollments,
208 TAG,
209 "update fpEnrollment"
210 )
211 }
212 }
213 }
214 authController.addCallback(callback)
215 trySendWithFailureLogging(
216 authController.isFingerprintEnrolled(currentUserId),
217 TAG,
218 "Initial value of fingerprint enrollment"
219 )
220 awaitClose { authController.removeCallback(callback) }
221 }
222 }
223
224 private val isFaceEnrolled: Flow<Boolean> =
selectedUserIdnull225 selectedUserId.flatMapLatest { selectedUserId: Int ->
226 conflatedCallbackFlow {
227 val callback =
228 object : AuthController.Callback {
229 override fun onEnrollmentsChanged(
230 sensorBiometricType: BiometricType,
231 userId: Int,
232 hasEnrollments: Boolean
233 ) {
234 if (sensorBiometricType == BiometricType.FACE) {
235 trySendWithFailureLogging(
236 authController.isFaceAuthEnrolled(selectedUserId),
237 TAG,
238 "Face enrollment changed"
239 )
240 }
241 }
242 }
243 authController.addCallback(callback)
244 trySendWithFailureLogging(
245 authController.isFaceAuthEnrolled(selectedUserId),
246 TAG,
247 "Initial value of face auth enrollment"
248 )
249 awaitClose { authController.removeCallback(callback) }
250 }
251 }
252
253 private val isFingerprintEnabledForCurrentUser: Flow<Boolean> =
userInfonull254 userRepository.selectedUserInfo.flatMapLatest { userInfo ->
255 areBiometricsEnabledForDeviceEntryFromUserSetting.map {
256 if (com.android.settings.flags.Flags.biometricsOnboardingEducation()) {
257 fingerprintEnabledForUser[userInfo.id] ?: false
258 } else {
259 biometricsEnabledForUser[userInfo.id] ?: false
260 }
261 }
262 }
263
264 private val isFaceEnabledForCurrentUser: Flow<Boolean> =
userInfonull265 userRepository.selectedUserInfo.flatMapLatest { userInfo ->
266 areBiometricsEnabledForDeviceEntryFromUserSetting.map {
267 if (com.android.settings.flags.Flags.biometricsOnboardingEducation()) {
268 faceEnabledForUser[userInfo.id] ?: false
269 } else {
270 biometricsEnabledForUser[userInfo.id] ?: false
271 }
272 }
273 }
274
275 private val isFaceEnabledByDevicePolicy: Flow<Boolean> =
userIdnull276 combine(selectedUserId, devicePolicyChangedForAllUsers) { userId, _ ->
277 devicePolicyManager.isFaceDisabled(userId)
278 }
<lambda>null279 .onStart {
280 emit(devicePolicyManager.isFaceDisabled(userRepository.getSelectedUserInfo().id))
281 }
282 .flowOn(backgroundDispatcher)
283 .distinctUntilChanged()
284
285 private val isFaceAuthenticationEnabled: Flow<Boolean> =
286 combine(isFaceEnabledForCurrentUser, isFaceEnabledByDevicePolicy) {
biometricsManagerSettingnull287 biometricsManagerSetting,
288 devicePolicySetting ->
289 biometricsManagerSetting && devicePolicySetting
290 }
291
292 private val areBiometricsEnabledForDeviceEntryFromUserSetting: Flow<Triple<Int, Boolean, Int>> =
293 callbackFlow {
294 val callback =
295 object : IBiometricEnabledOnKeyguardCallback.Stub() {
296 override fun onChanged(enabled: Boolean, userId: Int, modality: Int) {
297 trySendWithFailureLogging(
298 Triple(userId, enabled, modality),
299 TAG,
300 "biometricsEnabled state changed",
301 )
302 }
303 }
304 biometricManager?.registerEnabledOnKeyguardCallback(callback)
305 awaitClose {}
306 }
<lambda>null307 .onEach {
308 if (com.android.settings.flags.Flags.biometricsOnboardingEducation()) {
309 when (it.third) {
310 TYPE_FACE -> {
311 faceEnabledForUser[it.first] = it.second
312 }
313 TYPE_FINGERPRINT -> {
314 fingerprintEnabledForUser[it.first] = it.second
315 }
316 }
317 } else {
318 biometricsEnabledForUser[it.first] = it.second
319 }
320 }
321 // This is because the callback is binder-based and we want to avoid multiple callbacks
322 // being registered.
323 .stateIn(scope, SharingStarted.Eagerly, Triple(0, false, TYPE_NONE))
324
325 private val isStrongBiometricAllowed: StateFlow<Boolean> =
326 strongAuthTracker.isStrongBiometricAllowed.stateIn(
327 scope,
328 SharingStarted.Eagerly,
329 strongAuthTracker.isBiometricAllowedForUser(
330 true,
331 userRepository.getSelectedUserInfo().id
332 )
333 )
334
335 private val isNonStrongBiometricAllowed: StateFlow<Boolean> =
336 strongAuthTracker.isNonStrongBiometricAllowed.stateIn(
337 scope,
338 SharingStarted.Eagerly,
339 strongAuthTracker.isBiometricAllowedForUser(
340 false,
341 userRepository.getSelectedUserInfo().id
342 )
343 )
344
345 private val isFingerprintBiometricAllowed: Flow<Boolean> =
<lambda>null346 fingerprintPropertyRepository.strength.flatMapLatest {
347 if (it == SensorStrength.STRONG) isStrongBiometricAllowed
348 else isNonStrongBiometricAllowed
349 }
350
351 private val isFaceBiometricsAllowed: Flow<Boolean> =
<lambda>null352 facePropertyRepository.sensorInfo.flatMapLatest {
353 if (it?.strength == SensorStrength.STRONG) isStrongBiometricAllowed
354 else isNonStrongBiometricAllowed
355 }
356
357 private val isFingerprintEnabledByDevicePolicy: Flow<Boolean> =
userIdnull358 selectedUserId.flatMapLatest { userId ->
359 devicePolicyChangedForAllUsers
360 .transformLatest { emit(devicePolicyManager.isFingerprintDisabled(userId)) }
361 .onStart { emit(devicePolicyManager.isFingerprintDisabled(userId)) }
362 .flowOn(backgroundDispatcher)
363 .distinctUntilChanged()
364 }
365
366 override val isFingerprintEnrolledAndEnabled: StateFlow<Boolean> =
367 isFingerprintEnrolled
368 .and(isFingerprintEnabledForCurrentUser)
369 .and(isFingerprintEnabledByDevicePolicy)
370 .stateIn(scope, SharingStarted.Eagerly, false)
371
372 override val isFingerprintAuthCurrentlyAllowed: StateFlow<Boolean> =
373 isFingerprintEnrolledAndEnabled
374 .and(isFingerprintBiometricAllowed)
375 .stateIn(scope, SharingStarted.Eagerly, false)
376
377 override val isFaceAuthEnrolledAndEnabled: StateFlow<Boolean> =
378 isFaceAuthenticationEnabled
379 .and(isFaceEnrolled)
380 .and(mobileConnectionsRepository.isAnySimSecure.isFalse())
381 .stateIn(scope, SharingStarted.Eagerly, false)
382
383 override val isFaceAuthCurrentlyAllowed: Flow<Boolean> =
384 isFaceAuthEnrolledAndEnabled
385 .and(isFaceBiometricsAllowed)
386 .and(isFaceAuthSupportedInCurrentPosture)
387 }
388
389 private class StrongAuthTracker(
390 private val userRepository: UserRepository,
391 @ShadeDisplayAware context: Context?
392 ) :
393 LockPatternUtils.StrongAuthTracker(context) {
394
395 private val selectedUserId =
<lambda>null396 userRepository.selectedUserInfo.map { it.id }.distinctUntilChanged()
397
398 // Backing field for onStrongAuthRequiredChanged
399 private val _authFlags =
400 MutableStateFlow(AuthenticationFlags(currentUserId, getStrongAuthForUser(currentUserId)))
401
402 // Backing field for onIsNonStrongBiometricAllowedChanged
403 private val _nonStrongBiometricAllowed =
404 MutableStateFlow(
405 Pair(currentUserId, isNonStrongBiometricAllowedAfterIdleTimeout(currentUserId))
406 )
407
408 val currentUserAuthFlags: Flow<AuthenticationFlags> =
userIdnull409 selectedUserId.flatMapLatest { userId ->
410 _authFlags
411 .map { AuthenticationFlags(userId, getStrongAuthForUser(userId)) }
412 .onEach { Log.d(TAG, "currentUser authFlags changed, new value: $it") }
413 .onStart { emit(AuthenticationFlags(userId, getStrongAuthForUser(userId))) }
414 }
415
416 /** isStrongBiometricAllowed for the current user. */
417 val isStrongBiometricAllowed: Flow<Boolean> =
<lambda>null418 currentUserAuthFlags.map { isBiometricAllowedForUser(true, it.userId) }
419
420 /** isNonStrongBiometricAllowed for the current user. */
421 val isNonStrongBiometricAllowed: Flow<Boolean> =
422 selectedUserId
userIdnull423 .flatMapLatest { userId ->
424 _nonStrongBiometricAllowed
425 .filter { it.first == userId }
426 .map { it.second }
427 .onEach {
428 Log.d(TAG, "isNonStrongBiometricAllowed changed for current user: $it")
429 }
430 .onStart { emit(isNonStrongBiometricAllowedAfterIdleTimeout(userId)) }
431 }
432 .and(isStrongBiometricAllowed)
433
434 private val currentUserId
435 get() = userRepository.getSelectedUserInfo().id
436
onStrongAuthRequiredChangednull437 override fun onStrongAuthRequiredChanged(userId: Int) {
438 val newFlags = getStrongAuthForUser(userId)
439 _authFlags.value = AuthenticationFlags(userId, newFlags)
440 Log.d(TAG, "onStrongAuthRequiredChanged for userId: $userId, flag value: $newFlags")
441 }
442
onIsNonStrongBiometricAllowedChangednull443 override fun onIsNonStrongBiometricAllowedChanged(userId: Int) {
444 val allowed = isNonStrongBiometricAllowedAfterIdleTimeout(userId)
445 _nonStrongBiometricAllowed.value = Pair(userId, allowed)
446 Log.d(TAG, "onIsNonStrongBiometricAllowedChanged for userId: $userId, $allowed")
447 }
448 }
449
DevicePolicyManagernull450 private fun DevicePolicyManager.isFaceDisabled(userId: Int): Boolean =
451 isNotActive(userId, DevicePolicyManager.KEYGUARD_DISABLE_FACE)
452
453 private fun DevicePolicyManager.isFingerprintDisabled(userId: Int): Boolean =
454 isNotActive(userId, DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT)
455
456 private fun DevicePolicyManager.isNotActive(userId: Int, policy: Int): Boolean =
457 (getKeyguardDisabledFeatures(null, userId) and policy) == 0
458
459 private fun Flow<Boolean>.and(anotherFlow: Flow<Boolean>): Flow<Boolean> =
460 this.combine(anotherFlow) { a, b -> a && b }
461
<lambda>null462 private fun Flow<Boolean>.isFalse(): Flow<Boolean> = this.map { !it }
463