From 78a3bea4f2724e6cb0c7e2ebcfcbea020ce39528 Mon Sep 17 00:00:00 2001 From: pjf-dev <28768673+pjf-dev@users.noreply.github.com> Date: Sat, 19 Apr 2025 14:03:43 -0400 Subject: [PATCH 1/3] Fix incorrect error being reported in certain cases with fallback allowed --- .../src/main/java/BiometricActivity.kt | 53 ++++++++++--------- .../android/src/main/java/BiometricPlugin.kt | 2 +- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/plugins/biometric/android/src/main/java/BiometricActivity.kt b/plugins/biometric/android/src/main/java/BiometricActivity.kt index 011de4d5..bc6205db 100644 --- a/plugins/biometric/android/src/main/java/BiometricActivity.kt +++ b/plugins/biometric/android/src/main/java/BiometricActivity.kt @@ -38,41 +38,38 @@ class BiometricActivity : AppCompatActivity() { var title = intent.getStringExtra(BiometricPlugin.TITLE) val subtitle = intent.getStringExtra(BiometricPlugin.SUBTITLE) val description = intent.getStringExtra(BiometricPlugin.REASON) - allowDeviceCredential = false + allowDeviceCredential = intent.getBooleanExtra(BiometricPlugin.DEVICE_CREDENTIAL, false) + // Android docs say we should check if the device is secure before enabling device credential fallback val manager = getSystemService( Context.KEYGUARD_SERVICE ) as KeyguardManager - if (manager.isDeviceSecure) { - allowDeviceCredential = - intent.getBooleanExtra(BiometricPlugin.DEVICE_CREDENTIAL, false) - } - - if (title.isNullOrEmpty()) { - title = "Authenticate" - } - - builder.setTitle(title).setSubtitle(subtitle).setDescription(description) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - var authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK - if (allowDeviceCredential) { - authenticators = authenticators or BiometricManager.Authenticators.DEVICE_CREDENTIAL + if (allowDeviceCredential && manager.isDeviceSecure) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + var authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK + if (allowDeviceCredential) { + authenticators = authenticators or BiometricManager.Authenticators.DEVICE_CREDENTIAL + } + builder.setAllowedAuthenticators(authenticators) + } else { + @Suppress("DEPRECATION") + builder.setDeviceCredentialAllowed(allowDeviceCredential) } - builder.setAllowedAuthenticators(authenticators) } else { - @Suppress("DEPRECATION") - builder.setDeviceCredentialAllowed(allowDeviceCredential) - } - - // From the Android docs: - // You can't call setNegativeButtonText() and setAllowedAuthenticators(... or DEVICE_CREDENTIAL) - // at the same time on a BiometricPrompt.PromptInfo.Builder instance. - if (!allowDeviceCredential) { + // From the Android docs: + // You can't call setNegativeButtonText() and setAllowedAuthenticators(... or DEVICE_CREDENTIAL) + // at the same time on a BiometricPrompt.PromptInfo.Builder instance. val negativeButtonText = intent.getStringExtra(BiometricPlugin.CANCEL_TITLE) builder.setNegativeButtonText( if (negativeButtonText.isNullOrEmpty()) "Cancel" else negativeButtonText ) } + + if (title.isNullOrEmpty()) { + title = "Authenticate" + } + builder.setTitle(title).setSubtitle(subtitle).setDescription(description) + builder.setConfirmationRequired( intent.getBooleanExtra(BiometricPlugin.CONFIRMATION_REQUIRED, true) ) @@ -85,6 +82,14 @@ class BiometricActivity : AppCompatActivity() { errorCode: Int, errorMessage: CharSequence ) { + var errorCode = errorCode + var errorMessage = errorMessage + // override error to properly report no device credential if needed + if (allowDeviceCredential + && errorCode == BiometricPrompt.ERROR_NO_BIOMETRICS) { + errorCode = BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL + errorMessage = "No device credential set" + } super.onAuthenticationError(errorCode, errorMessage) finishActivity( BiometryResultType.ERROR, diff --git a/plugins/biometric/android/src/main/java/BiometricPlugin.kt b/plugins/biometric/android/src/main/java/BiometricPlugin.kt index b3436fd4..b9b7ab05 100644 --- a/plugins/biometric/android/src/main/java/BiometricPlugin.kt +++ b/plugins/biometric/android/src/main/java/BiometricPlugin.kt @@ -73,7 +73,7 @@ class BiometricPlugin(private val activity: Activity): Plugin(activity) { biometryErrorCodeMap[BiometricPrompt.ERROR_LOCKOUT_PERMANENT] = "biometryLockout" biometryErrorCodeMap[BiometricPrompt.ERROR_NEGATIVE_BUTTON] = "userCancel" biometryErrorCodeMap[BiometricPrompt.ERROR_NO_BIOMETRICS] = "biometryNotEnrolled" - biometryErrorCodeMap[BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL] = "noDeviceCredential" + biometryErrorCodeMap[BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL] = "passcodeNotSet" biometryErrorCodeMap[BiometricPrompt.ERROR_NO_SPACE] = "systemCancel" biometryErrorCodeMap[BiometricPrompt.ERROR_TIMEOUT] = "systemCancel" biometryErrorCodeMap[BiometricPrompt.ERROR_UNABLE_TO_PROCESS] = "systemCancel" From 9c3a8e7b1069c6dc3774045567223ec05ca47577 Mon Sep 17 00:00:00 2001 From: pjf-dev <28768673+pjf-dev@users.noreply.github.com> Date: Sat, 19 Apr 2025 14:18:56 -0400 Subject: [PATCH 2/3] Add change file --- .changes/fix-bio-fallback-error-android.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changes/fix-bio-fallback-error-android.md diff --git a/.changes/fix-bio-fallback-error-android.md b/.changes/fix-bio-fallback-error-android.md new file mode 100644 index 00000000..892a5c1e --- /dev/null +++ b/.changes/fix-bio-fallback-error-android.md @@ -0,0 +1,6 @@ +--- +"biometric": patch:bug +"biometric-js": patch:bug +--- + +Fix biometric plugin reporting an inconsistent and incorrect error on Android when no device passcode was set. \ No newline at end of file From 8b8036feabb05da153a55f659846f59e6707c9fb Mon Sep 17 00:00:00 2001 From: pjf-dev <28768673+pjf-dev@users.noreply.github.com> Date: Sat, 19 Apr 2025 19:10:00 -0400 Subject: [PATCH 3/3] Rework original solution due to overlooking authenticators logic on API >= `Build.VERSION_CODES.R` --- .../src/main/java/BiometricActivity.kt | 48 +++++++++---------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/plugins/biometric/android/src/main/java/BiometricActivity.kt b/plugins/biometric/android/src/main/java/BiometricActivity.kt index bc6205db..4f118775 100644 --- a/plugins/biometric/android/src/main/java/BiometricActivity.kt +++ b/plugins/biometric/android/src/main/java/BiometricActivity.kt @@ -38,38 +38,39 @@ class BiometricActivity : AppCompatActivity() { var title = intent.getStringExtra(BiometricPlugin.TITLE) val subtitle = intent.getStringExtra(BiometricPlugin.SUBTITLE) val description = intent.getStringExtra(BiometricPlugin.REASON) - allowDeviceCredential = intent.getBooleanExtra(BiometricPlugin.DEVICE_CREDENTIAL, false) - + val allowDeviceCredentialArg = intent.getBooleanExtra(BiometricPlugin.DEVICE_CREDENTIAL, false) // Android docs say we should check if the device is secure before enabling device credential fallback val manager = getSystemService( Context.KEYGUARD_SERVICE ) as KeyguardManager - if (allowDeviceCredential && manager.isDeviceSecure) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - var authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK - if (allowDeviceCredential) { - authenticators = authenticators or BiometricManager.Authenticators.DEVICE_CREDENTIAL - } - builder.setAllowedAuthenticators(authenticators) - } else { - @Suppress("DEPRECATION") - builder.setDeviceCredentialAllowed(allowDeviceCredential) + val allowDeviceCredential = allowDeviceCredentialArg && manager.isDeviceSecure; + + if (title.isNullOrEmpty()) { + title = "Authenticate" + } + + builder.setTitle(title).setSubtitle(subtitle).setDescription(description) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + var authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK + if (allowDeviceCredential) { + authenticators = authenticators or BiometricManager.Authenticators.DEVICE_CREDENTIAL } + builder.setAllowedAuthenticators(authenticators) } else { - // From the Android docs: - // You can't call setNegativeButtonText() and setAllowedAuthenticators(... or DEVICE_CREDENTIAL) - // at the same time on a BiometricPrompt.PromptInfo.Builder instance. + @Suppress("DEPRECATION") + builder.setDeviceCredentialAllowed(allowDeviceCredential) + } + + // From the Android docs: + // You can't call setNegativeButtonText() and setAllowedAuthenticators(... or DEVICE_CREDENTIAL) + // at the same time on a BiometricPrompt.PromptInfo.Builder instance. + if (!allowDeviceCredential) { val negativeButtonText = intent.getStringExtra(BiometricPlugin.CANCEL_TITLE) builder.setNegativeButtonText( if (negativeButtonText.isNullOrEmpty()) "Cancel" else negativeButtonText ) } - if (title.isNullOrEmpty()) { - title = "Authenticate" - } - builder.setTitle(title).setSubtitle(subtitle).setDescription(description) - builder.setConfirmationRequired( intent.getBooleanExtra(BiometricPlugin.CONFIRMATION_REQUIRED, true) ) @@ -85,8 +86,7 @@ class BiometricActivity : AppCompatActivity() { var errorCode = errorCode var errorMessage = errorMessage // override error to properly report no device credential if needed - if (allowDeviceCredential - && errorCode == BiometricPrompt.ERROR_NO_BIOMETRICS) { + if (allowDeviceCredentialArg && errorCode == BiometricPrompt.ERROR_NO_BIOMETRICS) { errorCode = BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL errorMessage = "No device credential set" } @@ -127,8 +127,4 @@ class BiometricActivity : AppCompatActivity() { setResult(Activity.RESULT_OK, intent) finish() } - - companion object { - var allowDeviceCredential = false - } } \ No newline at end of file