1 /*
2  * 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> =
146         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 {
163                     devicePostureRepository.currentDevicePosture.map {
164                         it == configFaceAuthSupportedPosture
165                     }
166                 }
167                 .onEach { Log.d(TAG, "isFaceAuthSupportedInCurrentPosture value changed to: $it") }
168     }
169 
170     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> =
180         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
190             .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> =
220         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> =
249         userRepository.selectedUserInfo.flatMapLatest { userInfo ->
250             isFaceEnabledByBiometricsManager.map { biometricsEnabledForUser[userInfo.id] ?: false }
251         }
252 
253     private val isFaceEnabledByDevicePolicy: Flow<Boolean> =
254         combine(selectedUserId, devicePolicyChangedForAllUsers) { userId, _ ->
255                 devicePolicyManager.isFaceDisabled(userId)
256             }
257             .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) {
265             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             }
285             .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> =
311         fingerprintPropertyRepository.strength.flatMapLatest {
312             if (it == SensorStrength.STRONG) isStrongBiometricAllowed
313             else isNonStrongBiometricAllowed
314         }
315 
316     private val isFaceBiometricsAllowed: Flow<Boolean> =
317         facePropertyRepository.sensorInfo.flatMapLatest {
318             if (it?.strength == SensorStrength.STRONG) isStrongBiometricAllowed
319             else isNonStrongBiometricAllowed
320         }
321 
322     private val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean> =
323         selectedUserId
324             .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 =
364         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> =
377         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> =
386         currentUserAuthFlags.map { isBiometricAllowedForUser(true, it.userId) }
387 
388     /** isNonStrongBiometricAllowed for the current user. */
389     val isNonStrongBiometricAllowed: Flow<Boolean> =
390         selectedUserId
391             .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 
405     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 
411     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 
418 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