1 /* 2 * Copyright 2024 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 package androidx.credentials.provider 17 18 import android.hardware.biometrics.BiometricPrompt 19 import android.util.Log 20 import androidx.annotation.RestrictTo 21 import java.util.Objects 22 import org.jetbrains.annotations.VisibleForTesting 23 24 /** 25 * Error returned from the Biometric Prompt flow that is executed by 26 * [androidx.credentials.CredentialManager] after the user makes a selection on the Credential 27 * Manager account selector. 28 * 29 * @property errorCode the error code denoting what kind of error was encountered while the 30 * biometric prompt flow failed, must be one of the error codes defined in 31 * [androidx.biometric.BiometricPrompt] such as 32 * [androidx.biometric.BiometricPrompt.ERROR_HW_UNAVAILABLE] or 33 * [androidx.biometric.BiometricPrompt.ERROR_TIMEOUT] 34 * @property errorMsg the message associated with the [errorCode] in the form that can be displayed 35 * on a UI. 36 * @see AuthenticationErrorTypes 37 */ 38 class AuthenticationError 39 @JvmOverloads 40 constructor( 41 val errorCode: @AuthenticationErrorTypes Int, 42 val errorMsg: CharSequence? = null, 43 ) { 44 internal companion object { 45 internal val TAG = "AuthenticationError" 46 @VisibleForTesting 47 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 48 const val EXTRA_BIOMETRIC_AUTH_ERROR = 49 "androidx.credentials.provider.BIOMETRIC_AUTH_ERROR_CODE" 50 @VisibleForTesting 51 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 52 const val EXTRA_BIOMETRIC_AUTH_ERROR_FALLBACK = "BIOMETRIC_AUTH_ERROR_CODE" 53 @VisibleForTesting 54 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 55 const val EXTRA_BIOMETRIC_AUTH_ERROR_MESSAGE = 56 "androidx.credentials.provider.BIOMETRIC_AUTH_ERROR_MESSAGE" 57 @VisibleForTesting 58 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 59 const val EXTRA_BIOMETRIC_AUTH_ERROR_MESSAGE_FALLBACK = "BIOMETRIC_AUTH_ERROR_MESSAGE" 60 // The majority of this is unexpected to be sent, or the values are equal, 61 // but should it arrive for any reason, is handled properly. This way 62 // providers can be confident the Jetpack codes alone are enough. 63 @VisibleForTesting 64 internal val biometricFrameworkToJetpackErrorMap = 65 linkedMapOf( 66 BiometricPrompt.BIOMETRIC_ERROR_CANCELED to 67 androidx.biometric.BiometricPrompt.ERROR_CANCELED, 68 BiometricPrompt.BIOMETRIC_ERROR_HW_NOT_PRESENT to 69 androidx.biometric.BiometricPrompt.ERROR_HW_NOT_PRESENT, 70 BiometricPrompt.BIOMETRIC_ERROR_HW_UNAVAILABLE to 71 androidx.biometric.BiometricPrompt.ERROR_HW_UNAVAILABLE, 72 BiometricPrompt.BIOMETRIC_ERROR_LOCKOUT to 73 androidx.biometric.BiometricPrompt.ERROR_LOCKOUT, 74 BiometricPrompt.BIOMETRIC_ERROR_LOCKOUT_PERMANENT to 75 androidx.biometric.BiometricPrompt.ERROR_LOCKOUT_PERMANENT, 76 BiometricPrompt.BIOMETRIC_ERROR_NO_BIOMETRICS to 77 androidx.biometric.BiometricPrompt.ERROR_NO_BIOMETRICS, 78 BiometricPrompt.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL to 79 androidx.biometric.BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL, 80 BiometricPrompt.BIOMETRIC_ERROR_NO_SPACE to 81 androidx.biometric.BiometricPrompt.ERROR_NO_SPACE, 82 BiometricPrompt.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED to 83 androidx.biometric.BiometricPrompt.ERROR_SECURITY_UPDATE_REQUIRED, 84 BiometricPrompt.BIOMETRIC_ERROR_TIMEOUT to 85 androidx.biometric.BiometricPrompt.ERROR_TIMEOUT, 86 BiometricPrompt.BIOMETRIC_ERROR_UNABLE_TO_PROCESS to 87 androidx.biometric.BiometricPrompt.ERROR_UNABLE_TO_PROCESS, 88 BiometricPrompt.BIOMETRIC_ERROR_USER_CANCELED to 89 androidx.biometric.BiometricPrompt.ERROR_USER_CANCELED, 90 BiometricPrompt.BIOMETRIC_ERROR_VENDOR to 91 androidx.biometric.BiometricPrompt.ERROR_VENDOR 92 // TODO(b/340334264) : Add NEGATIVE_BUTTON from FW once avail, or wrap this in 93 // a credential manager specific error. 94 ) 95 convertFrameworkBiometricErrorToJetpacknull96 internal fun convertFrameworkBiometricErrorToJetpack(frameworkCode: Int): Int { 97 // Ignoring getOrDefault to allow this object down to API 21 98 return if (biometricFrameworkToJetpackErrorMap.containsKey(frameworkCode)) { 99 biometricFrameworkToJetpackErrorMap[frameworkCode]!! 100 } else { 101 Log.i(TAG, "Unexpected error code, $frameworkCode, ") 102 frameworkCode 103 } 104 } 105 106 /** 107 * Generates an instance of this class, to be called by an UI consumer that calls 108 * [BiometricPrompt] API and needs the result to be wrapped by this class. The caller of 109 * this API must specify whether the framework [android.hardware.biometrics.BiometricPrompt] 110 * API or the jetpack [androidx.biometric.BiometricPrompt] API is used through 111 * [isFrameworkBiometricPrompt]. 112 * 113 * @param uiErrorCode the error code used to create this error instance, typically using the 114 * [androidx.biometric.BiometricPrompt]'s constants if conversion isn't desired, or 115 * [android.hardware.biometrics.BiometricPrompt]'s constants if conversion *is* desired. 116 * @param uiErrorMessage the message associated with the [uiErrorCode] in the form that can 117 * be displayed on a UI. 118 * @param isFrameworkBiometricPrompt the bit indicating whether or not this error code 119 * requires conversion or not, set to true by default 120 * @return an authentication error that has properly handled conversion of the err code 121 */ 122 @JvmOverloads 123 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) createFromnull124 internal fun createFrom( 125 uiErrorCode: Int, 126 uiErrorMessage: CharSequence, 127 isFrameworkBiometricPrompt: Boolean = true, 128 ): AuthenticationError = 129 AuthenticationError( 130 errorCode = 131 if (isFrameworkBiometricPrompt) 132 convertFrameworkBiometricErrorToJetpack(uiErrorCode) 133 else uiErrorCode, 134 errorMsg = uiErrorMessage, 135 ) 136 } 137 138 override fun equals(other: Any?): Boolean { 139 if (this === other) { 140 return true 141 } 142 if (other is AuthenticationError) { 143 return this.errorCode == other.errorCode && this.errorMsg == other.errorMsg 144 } 145 return false 146 } 147 hashCodenull148 override fun hashCode(): Int { 149 return Objects.hash(this.errorCode, this.errorMsg) 150 } 151 } 152