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