• 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.BiometricManager
24 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback
25 import android.os.Looper
26 import android.os.UserHandle
27 import android.util.Log
28 import com.android.internal.widget.LockPatternUtils
29 import com.android.systemui.Dumpable
30 import com.android.systemui.R
31 import com.android.systemui.biometrics.AuthController
32 import com.android.systemui.broadcast.BroadcastDispatcher
33 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
34 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
35 import com.android.systemui.dagger.SysUISingleton
36 import com.android.systemui.dagger.qualifiers.Application
37 import com.android.systemui.dagger.qualifiers.Background
38 import com.android.systemui.dagger.qualifiers.Main
39 import com.android.systemui.dump.DumpManager
40 import com.android.systemui.keyguard.shared.model.DevicePosture
41 import com.android.systemui.user.data.repository.UserRepository
42 import java.io.PrintWriter
43 import javax.inject.Inject
44 import kotlinx.coroutines.CoroutineDispatcher
45 import kotlinx.coroutines.CoroutineScope
46 import kotlinx.coroutines.channels.awaitClose
47 import kotlinx.coroutines.flow.Flow
48 import kotlinx.coroutines.flow.SharingStarted
49 import kotlinx.coroutines.flow.StateFlow
50 import kotlinx.coroutines.flow.combine
51 import kotlinx.coroutines.flow.distinctUntilChanged
52 import kotlinx.coroutines.flow.flatMapLatest
53 import kotlinx.coroutines.flow.flowOf
54 import kotlinx.coroutines.flow.flowOn
55 import kotlinx.coroutines.flow.map
56 import kotlinx.coroutines.flow.onEach
57 import kotlinx.coroutines.flow.onStart
58 import kotlinx.coroutines.flow.stateIn
59 import kotlinx.coroutines.flow.transformLatest
60 
61 /**
62  * Acts as source of truth for biometric authentication related settings like enrollments, device
63  * policy, etc.
64  *
65  * Abstracts-away data sources and their schemas so the rest of the app doesn't need to worry about
66  * upstream changes.
67  */
68 interface BiometricSettingsRepository {
69     /** Whether any fingerprints are enrolled for the current user. */
70     val isFingerprintEnrolled: StateFlow<Boolean>
71 
72     /** Whether face authentication is enrolled for the current user. */
73     val isFaceEnrolled: Flow<Boolean>
74 
75     /**
76      * Whether face authentication is enabled/disabled based on system settings like device policy,
77      * biometrics setting.
78      */
79     val isFaceAuthenticationEnabled: Flow<Boolean>
80 
81     /**
82      * Whether the current user is allowed to use a strong biometric for device entry based on
83      * Android Security policies. If false, the user may be able to use primary authentication for
84      * device entry.
85      */
86     val isStrongBiometricAllowed: StateFlow<Boolean>
87 
88     /** Whether fingerprint feature is enabled for the current user by the DevicePolicy */
89     val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean>
90 
91     /**
92      * Whether face authentication is supported for the current device posture. Face auth can be
93      * restricted to specific postures using [R.integer.config_face_auth_supported_posture]
94      */
95     val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>
96 }
97 
98 @SysUISingleton
99 class BiometricSettingsRepositoryImpl
100 @Inject
101 constructor(
102     context: Context,
103     lockPatternUtils: LockPatternUtils,
104     broadcastDispatcher: BroadcastDispatcher,
105     authController: AuthController,
106     userRepository: UserRepository,
107     devicePolicyManager: DevicePolicyManager,
108     @Application scope: CoroutineScope,
109     @Background backgroundDispatcher: CoroutineDispatcher,
110     biometricManager: BiometricManager?,
111     @Main looper: Looper,
112     devicePostureRepository: DevicePostureRepository,
113     dumpManager: DumpManager,
114 ) : BiometricSettingsRepository, Dumpable {
115 
116     override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>
117 
118     init {
119         dumpManager.registerDumpable(this)
120         val configFaceAuthSupportedPosture =
121             DevicePosture.toPosture(
122                 context.resources.getInteger(R.integer.config_face_auth_supported_posture)
123             )
124         isFaceAuthSupportedInCurrentPosture =
125             if (configFaceAuthSupportedPosture == DevicePosture.UNKNOWN) {
126                     flowOf(true)
127                 } else {
<lambda>null128                     devicePostureRepository.currentDevicePosture.map {
129                         it == configFaceAuthSupportedPosture
130                     }
131                 }
<lambda>null132                 .onEach { Log.d(TAG, "isFaceAuthSupportedInCurrentPosture value changed to: $it") }
133     }
134 
dumpnull135     override fun dump(pw: PrintWriter, args: Array<String?>) {
136         pw.println("isFingerprintEnrolled=${isFingerprintEnrolled.value}")
137         pw.println("isStrongBiometricAllowed=${isStrongBiometricAllowed.value}")
138         pw.println("isFingerprintEnabledByDevicePolicy=${isFingerprintEnabledByDevicePolicy.value}")
139     }
140 
141     /** UserId of the current selected user. */
142     private val selectedUserId: Flow<Int> =
<lambda>null143         userRepository.selectedUserInfo.map { it.id }.distinctUntilChanged()
144 
145     private val devicePolicyChangedForAllUsers =
146         broadcastDispatcher.broadcastFlow(
147             filter = IntentFilter(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
148             user = UserHandle.ALL
149         )
150 
151     override val isFingerprintEnrolled: StateFlow<Boolean> =
152         selectedUserId
currentUserIdnull153             .flatMapLatest { currentUserId ->
154                 conflatedCallbackFlow {
155                     val callback =
156                         object : AuthController.Callback {
157                             override fun onEnrollmentsChanged(
158                                 sensorBiometricType: BiometricType,
159                                 userId: Int,
160                                 hasEnrollments: Boolean
161                             ) {
162                                 if (sensorBiometricType.isFingerprint && userId == currentUserId) {
163                                     trySendWithFailureLogging(
164                                         hasEnrollments,
165                                         TAG,
166                                         "update fpEnrollment"
167                                     )
168                                 }
169                             }
170                         }
171                     authController.addCallback(callback)
172                     awaitClose { authController.removeCallback(callback) }
173                 }
174             }
175             .stateIn(
176                 scope,
177                 started = SharingStarted.Eagerly,
178                 initialValue =
179                     authController.isFingerprintEnrolled(userRepository.getSelectedUserInfo().id)
180             )
181 
182     override val isFaceEnrolled: Flow<Boolean> =
selectedUserIdnull183         selectedUserId.flatMapLatest { selectedUserId: Int ->
184             conflatedCallbackFlow {
185                 val callback =
186                     object : AuthController.Callback {
187                         override fun onEnrollmentsChanged(
188                             sensorBiometricType: BiometricType,
189                             userId: Int,
190                             hasEnrollments: Boolean
191                         ) {
192                             // TODO(b/242022358), use authController.isFaceAuthEnrolled after
193                             //  ag/20176811 is available.
194                             if (
195                                 sensorBiometricType == BiometricType.FACE &&
196                                     userId == selectedUserId
197                             ) {
198                                 trySendWithFailureLogging(
199                                     hasEnrollments,
200                                     TAG,
201                                     "Face enrollment changed"
202                                 )
203                             }
204                         }
205                     }
206                 authController.addCallback(callback)
207                 trySendWithFailureLogging(
208                     authController.isFaceAuthEnrolled(selectedUserId),
209                     TAG,
210                     "Initial value of face auth enrollment"
211                 )
212                 awaitClose { authController.removeCallback(callback) }
213             }
214         }
215 
216     override val isFaceAuthenticationEnabled: Flow<Boolean>
217         get() =
218             combine(isFaceEnabledByBiometricsManager, isFaceEnabledByDevicePolicy) {
biometricsManagerSettingnull219                 biometricsManagerSetting,
220                 devicePolicySetting ->
221                 biometricsManagerSetting && devicePolicySetting
222             }
223 
224     private val isFaceEnabledByDevicePolicy: Flow<Boolean> =
225         combine(selectedUserId, devicePolicyChangedForAllUsers) { userId, _ ->
226                 devicePolicyManager.isFaceDisabled(userId)
227             }
<lambda>null228             .onStart {
229                 emit(devicePolicyManager.isFaceDisabled(userRepository.getSelectedUserInfo().id))
230             }
231             .flowOn(backgroundDispatcher)
232             .distinctUntilChanged()
233 
234     private val isFaceEnabledByBiometricsManager =
<lambda>null235         conflatedCallbackFlow {
236                 val callback =
237                     object : IBiometricEnabledOnKeyguardCallback.Stub() {
238                         override fun onChanged(enabled: Boolean, userId: Int) {
239                             trySendWithFailureLogging(
240                                 enabled,
241                                 TAG,
242                                 "biometricsEnabled state changed"
243                             )
244                         }
245                     }
246                 biometricManager?.registerEnabledOnKeyguardCallback(callback)
247                 awaitClose {}
248             }
249             // This is because the callback is binder-based and we want to avoid multiple callbacks
250             // being registered.
251             .stateIn(scope, SharingStarted.Eagerly, false)
252 
253     override val isStrongBiometricAllowed: StateFlow<Boolean> =
254         selectedUserId
currUserIdnull255             .flatMapLatest { currUserId ->
256                 conflatedCallbackFlow {
257                     val callback =
258                         object : LockPatternUtils.StrongAuthTracker(context, looper) {
259                             override fun onStrongAuthRequiredChanged(userId: Int) {
260                                 if (currUserId != userId) {
261                                     return
262                                 }
263 
264                                 trySendWithFailureLogging(
265                                     isBiometricAllowedForUser(true, currUserId),
266                                     TAG
267                                 )
268                             }
269 
270                             override fun onIsNonStrongBiometricAllowedChanged(userId: Int) {
271                                 // no-op
272                             }
273                         }
274                     lockPatternUtils.registerStrongAuthTracker(callback)
275                     awaitClose { lockPatternUtils.unregisterStrongAuthTracker(callback) }
276                 }
277             }
278             .stateIn(
279                 scope,
280                 started = SharingStarted.Eagerly,
281                 initialValue =
282                     lockPatternUtils.isBiometricAllowedForUser(
283                         userRepository.getSelectedUserInfo().id
284                     )
285             )
286 
287     override val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean> =
288         selectedUserId
userIdnull289             .flatMapLatest { userId ->
290                 devicePolicyChangedForAllUsers
291                     .transformLatest { emit(devicePolicyManager.isFingerprintDisabled(userId)) }
292                     .flowOn(backgroundDispatcher)
293                     .distinctUntilChanged()
294             }
295             .stateIn(
296                 scope,
297                 started = SharingStarted.Eagerly,
298                 initialValue =
299                     devicePolicyManager.isFingerprintDisabled(
300                         userRepository.getSelectedUserInfo().id
301                     )
302             )
303 
304     companion object {
305         private const val TAG = "BiometricsRepositoryImpl"
306     }
307 }
308 
DevicePolicyManagernull309 private fun DevicePolicyManager.isFaceDisabled(userId: Int): Boolean =
310     isNotActive(userId, DevicePolicyManager.KEYGUARD_DISABLE_FACE)
311 
312 private fun DevicePolicyManager.isFingerprintDisabled(userId: Int): Boolean =
313     isNotActive(userId, DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT)
314 
315 private fun DevicePolicyManager.isNotActive(userId: Int, policy: Int): Boolean =
316     (getKeyguardDisabledFeatures(null, userId) and policy) == 0
317