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