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