1 package com.android.systemui.biometrics.domain.interactor 2 3 import android.app.admin.DevicePolicyManager 4 import android.app.admin.DevicePolicyResources 5 import android.content.Context 6 import android.os.UserManager 7 import com.android.internal.widget.LockPatternUtils 8 import com.android.internal.widget.LockscreenCredential 9 import com.android.internal.widget.VerifyCredentialResponse 10 import com.android.systemui.R 11 import com.android.systemui.biometrics.domain.model.BiometricPromptRequest 12 import com.android.systemui.dagger.qualifiers.Application 13 import com.android.systemui.util.time.SystemClock 14 import javax.inject.Inject 15 import kotlinx.coroutines.delay 16 import kotlinx.coroutines.flow.Flow 17 import kotlinx.coroutines.flow.flow 18 19 /** 20 * A wrapper for [LockPatternUtils] to verify PIN, pattern, or password credentials. 21 * 22 * This class also uses the [DevicePolicyManager] to generate appropriate error messages when policy 23 * exceptions are raised (i.e. wipe device due to excessive failed attempts, etc.). 24 */ 25 interface CredentialInteractor { 26 /** If the user's pattern credential should be hidden */ 27 fun isStealthModeActive(userId: Int): Boolean 28 29 /** Get the effective user id (profile owner, if one exists) */ 30 fun getCredentialOwnerOrSelfId(userId: Int): Int 31 32 /** 33 * Verifies a credential and returns a stream of results. 34 * 35 * The final emitted value will either be a [CredentialStatus.Fail.Error] or a 36 * [CredentialStatus.Success.Verified]. 37 */ 38 fun verifyCredential( 39 request: BiometricPromptRequest.Credential, 40 credential: LockscreenCredential, 41 ): Flow<CredentialStatus> 42 } 43 44 /** Standard implementation of [CredentialInteractor]. */ 45 class CredentialInteractorImpl 46 @Inject 47 constructor( 48 @Application private val applicationContext: Context, 49 private val lockPatternUtils: LockPatternUtils, 50 private val userManager: UserManager, 51 private val devicePolicyManager: DevicePolicyManager, 52 private val systemClock: SystemClock, 53 ) : CredentialInteractor { 54 55 override fun isStealthModeActive(userId: Int): Boolean = 56 !lockPatternUtils.isVisiblePatternEnabled(userId) 57 58 override fun getCredentialOwnerOrSelfId(userId: Int): Int = 59 userManager.getCredentialOwnerProfile(userId) 60 61 override fun verifyCredential( 62 request: BiometricPromptRequest.Credential, 63 credential: LockscreenCredential, 64 ): Flow<CredentialStatus> = flow { 65 // Request LockSettingsService to return the Gatekeeper Password in the 66 // VerifyCredentialResponse so that we can request a Gatekeeper HAT with the 67 // Gatekeeper Password and operationId. 68 val effectiveUserId = request.userInfo.deviceCredentialOwnerId 69 val response = 70 lockPatternUtils.verifyCredential( 71 credential, 72 effectiveUserId, 73 LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE 74 ) 75 76 if (response.isMatched) { 77 lockPatternUtils.userPresent(effectiveUserId) 78 79 // The response passed into this method contains the Gatekeeper 80 // Password. We still have to request Gatekeeper to create a 81 // Hardware Auth Token with the Gatekeeper Password and Challenge 82 // (keystore operationId in this case) 83 val pwHandle = response.gatekeeperPasswordHandle 84 val gkResponse: VerifyCredentialResponse = 85 lockPatternUtils.verifyGatekeeperPasswordHandle( 86 pwHandle, 87 request.operationInfo.gatekeeperChallenge, 88 effectiveUserId 89 ) 90 val hat = gkResponse.gatekeeperHAT 91 lockPatternUtils.removeGatekeeperPasswordHandle(pwHandle) 92 emit(CredentialStatus.Success.Verified(checkNotNull(hat))) 93 } else if (response.timeout > 0) { 94 // if requests are being throttled, update the error message every 95 // second until the temporary lock has expired 96 val deadline: Long = 97 lockPatternUtils.setLockoutAttemptDeadline(effectiveUserId, response.timeout) 98 val interval = LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS 99 var remaining = deadline - systemClock.elapsedRealtime() 100 while (remaining > 0) { 101 emit( 102 CredentialStatus.Fail.Throttled( 103 applicationContext.getString( 104 R.string.biometric_dialog_credential_too_many_attempts, 105 remaining / 1000 106 ) 107 ) 108 ) 109 delay(interval) 110 remaining -= interval 111 } 112 emit(CredentialStatus.Fail.Error("")) 113 } else { // bad request, but not throttled 114 val numAttempts = lockPatternUtils.getCurrentFailedPasswordAttempts(effectiveUserId) + 1 115 val maxAttempts = lockPatternUtils.getMaximumFailedPasswordsForWipe(effectiveUserId) 116 if (maxAttempts <= 0 || numAttempts <= 0) { 117 // use a generic message if there's no maximum number of attempts 118 emit(CredentialStatus.Fail.Error()) 119 } else { 120 val remainingAttempts = (maxAttempts - numAttempts).coerceAtLeast(0) 121 emit( 122 CredentialStatus.Fail.Error( 123 applicationContext.getString( 124 R.string.biometric_dialog_credential_attempts_before_wipe, 125 numAttempts, 126 maxAttempts 127 ), 128 remainingAttempts, 129 fetchFinalAttemptMessageOrNull(request, remainingAttempts) 130 ) 131 ) 132 } 133 lockPatternUtils.reportFailedPasswordAttempt(effectiveUserId) 134 } 135 } 136 137 private fun fetchFinalAttemptMessageOrNull( 138 request: BiometricPromptRequest.Credential, 139 remainingAttempts: Int?, 140 ): String? = 141 if (remainingAttempts != null && remainingAttempts <= 1) { 142 applicationContext.getFinalAttemptMessageOrBlank( 143 request, 144 devicePolicyManager, 145 userManager.getUserTypeForWipe( 146 devicePolicyManager, 147 request.userInfo.deviceCredentialOwnerId 148 ), 149 remainingAttempts 150 ) 151 } else { 152 null 153 } 154 } 155 156 private enum class UserType { 157 PRIMARY, 158 MANAGED_PROFILE, 159 SECONDARY, 160 } 161 162 private fun UserManager.getUserTypeForWipe( 163 devicePolicyManager: DevicePolicyManager, 164 effectiveUserId: Int, 165 ): UserType { 166 val userToBeWiped = 167 getUserInfo( 168 devicePolicyManager.getProfileWithMinimumFailedPasswordsForWipe(effectiveUserId) 169 ) 170 return when { 171 userToBeWiped == null || userToBeWiped.isPrimary -> UserType.PRIMARY 172 userToBeWiped.isManagedProfile -> UserType.MANAGED_PROFILE 173 else -> UserType.SECONDARY 174 } 175 } 176 177 private fun Context.getFinalAttemptMessageOrBlank( 178 request: BiometricPromptRequest.Credential, 179 devicePolicyManager: DevicePolicyManager, 180 userType: UserType, 181 remaining: Int, 182 ): String = 183 when { 184 remaining == 1 -> getLastAttemptBeforeWipeMessage(request, devicePolicyManager, userType) 185 remaining <= 0 -> getNowWipingMessage(devicePolicyManager, userType) 186 else -> "" 187 } 188 189 private fun Context.getLastAttemptBeforeWipeMessage( 190 request: BiometricPromptRequest.Credential, 191 devicePolicyManager: DevicePolicyManager, 192 userType: UserType, 193 ): String = 194 when (userType) { 195 UserType.PRIMARY -> getLastAttemptBeforeWipeDeviceMessage(request) 196 UserType.MANAGED_PROFILE -> 197 getLastAttemptBeforeWipeProfileMessage(request, devicePolicyManager) 198 UserType.SECONDARY -> getLastAttemptBeforeWipeUserMessage(request) 199 } 200 201 private fun Context.getLastAttemptBeforeWipeDeviceMessage( 202 request: BiometricPromptRequest.Credential, 203 ): String { 204 val id = 205 when (request) { 206 is BiometricPromptRequest.Credential.Pin -> 207 R.string.biometric_dialog_last_pin_attempt_before_wipe_device 208 is BiometricPromptRequest.Credential.Pattern -> 209 R.string.biometric_dialog_last_pattern_attempt_before_wipe_device 210 is BiometricPromptRequest.Credential.Password -> 211 R.string.biometric_dialog_last_password_attempt_before_wipe_device 212 } 213 return getString(id) 214 } 215 216 private fun Context.getLastAttemptBeforeWipeProfileMessage( 217 request: BiometricPromptRequest.Credential, 218 devicePolicyManager: DevicePolicyManager, 219 ): String { 220 val id = 221 when (request) { 222 is BiometricPromptRequest.Credential.Pin -> 223 DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PIN_LAST_ATTEMPT 224 is BiometricPromptRequest.Credential.Pattern -> 225 DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT 226 is BiometricPromptRequest.Credential.Password -> 227 DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT 228 } 229 val getFallbackString = { 230 val defaultId = 231 when (request) { 232 is BiometricPromptRequest.Credential.Pin -> 233 R.string.biometric_dialog_last_pin_attempt_before_wipe_profile 234 is BiometricPromptRequest.Credential.Pattern -> 235 R.string.biometric_dialog_last_pattern_attempt_before_wipe_profile 236 is BiometricPromptRequest.Credential.Password -> 237 R.string.biometric_dialog_last_password_attempt_before_wipe_profile 238 } 239 getString(defaultId) 240 } 241 242 return devicePolicyManager.resources?.getString(id, getFallbackString) ?: getFallbackString() 243 } 244 245 private fun Context.getLastAttemptBeforeWipeUserMessage( 246 request: BiometricPromptRequest.Credential, 247 ): String { 248 val resId = 249 when (request) { 250 is BiometricPromptRequest.Credential.Pin -> 251 R.string.biometric_dialog_last_pin_attempt_before_wipe_user 252 is BiometricPromptRequest.Credential.Pattern -> 253 R.string.biometric_dialog_last_pattern_attempt_before_wipe_user 254 is BiometricPromptRequest.Credential.Password -> 255 R.string.biometric_dialog_last_password_attempt_before_wipe_user 256 } 257 return getString(resId) 258 } 259 260 private fun Context.getNowWipingMessage( 261 devicePolicyManager: DevicePolicyManager, 262 userType: UserType, 263 ): String { 264 val id = 265 when (userType) { 266 UserType.MANAGED_PROFILE -> 267 DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS 268 else -> DevicePolicyResources.UNDEFINED 269 } 270 271 val getFallbackString = { 272 val defaultId = 273 when (userType) { 274 UserType.PRIMARY -> 275 com.android.settingslib.R.string.failed_attempts_now_wiping_device 276 UserType.MANAGED_PROFILE -> 277 com.android.settingslib.R.string.failed_attempts_now_wiping_profile 278 UserType.SECONDARY -> 279 com.android.settingslib.R.string.failed_attempts_now_wiping_user 280 } 281 getString(defaultId) 282 } 283 284 return devicePolicyManager.resources?.getString(id, getFallbackString) ?: getFallbackString() 285 } 286