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