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