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