commit 06476316050682dfd16f0f12c3599cf298c7e7ba
parent 8150b6fa6581391808fff7ca684c58784195783b
Author: Iván Ávalos <avalos@disroot.org>
Date: Wed, 6 Aug 2025 16:19:46 +0200
[wallet] app lock support for older Android versions (fixed crash)
Diffstat:
2 files changed, 77 insertions(+), 31 deletions(-)
diff --git a/wallet/src/main/java/net/taler/wallet/MainActivity.kt b/wallet/src/main/java/net/taler/wallet/MainActivity.kt
@@ -36,6 +36,9 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
import androidx.biometric.BiometricPrompt
+import androidx.biometric.BiometricPrompt.ERROR_NO_BIOMETRICS
+import androidx.biometric.BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL
+import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
@@ -216,13 +219,15 @@ class MainActivity : AppCompatActivity(), OnPreferenceStartFragmentCallback {
biometricPrompt.authenticate(promptInfo)
}
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) return
biometricPrompt = BiometricPrompt(
this,
- mainExecutor,
+ ContextCompat.getMainExecutor(this),
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
+ if (errorCode == ERROR_NO_BIOMETRICS || errorCode == ERROR_NO_DEVICE_CREDENTIAL) {
+ model.unlockWallet()
+ }
Toast.makeText(this@MainActivity, getString(R.string.biometric_auth_error, errString), LENGTH_SHORT).show()
}
@@ -238,11 +243,19 @@ class MainActivity : AppCompatActivity(), OnPreferenceStartFragmentCallback {
},
)
- promptInfo = BiometricPrompt.PromptInfo.Builder()
- .setTitle(getString(R.string.biometric_prompt_title))
- .setAllowedAuthenticators(BIOMETRIC_STRONG or DEVICE_CREDENTIAL)
- .setConfirmationRequired(true)
- .build()
+ promptInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ BiometricPrompt.PromptInfo.Builder()
+ .setTitle(getString(R.string.biometric_prompt_title))
+ .setAllowedAuthenticators(BIOMETRIC_STRONG or DEVICE_CREDENTIAL)
+ .setConfirmationRequired(true)
+ .build()
+ } else {
+ BiometricPrompt.PromptInfo.Builder()
+ .setTitle(getString(R.string.biometric_prompt_title))
+ .setDeviceCredentialAllowed(true)
+ .setConfirmationRequired(true)
+ .build()
+ }
}
override fun onNewIntent(intent: Intent) {
diff --git a/wallet/src/main/java/net/taler/wallet/settings/SettingsFragment.kt b/wallet/src/main/java/net/taler/wallet/settings/SettingsFragment.kt
@@ -17,10 +17,14 @@
package net.taler.wallet.settings
import android.app.Activity.RESULT_OK
+import android.app.KeyguardManager
+import android.content.Context.KEYGUARD_SERVICE
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.provider.Settings.ACTION_BIOMETRIC_ENROLL
+import android.provider.Settings.ACTION_FINGERPRINT_ENROLL
+import android.provider.Settings.ACTION_SECURITY_SETTINGS
import android.provider.Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED
import android.view.View
import android.widget.Toast
@@ -137,10 +141,6 @@ class SettingsFragment : PreferenceFragmentCompat() {
model.exchangeVersion?.let { prefVersionExchange.summary = it }
model.merchantVersion?.let { prefVersionMerchant.summary = it }
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
- prefBiometricLock.isVisible = false
- }
-
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
settingsManager.getBiometricLockEnabled(requireContext()).collect { enabled ->
@@ -227,41 +227,74 @@ class SettingsFragment : PreferenceFragmentCompat() {
}
private fun enableBiometrics(prompt: Boolean): Boolean {
- when (biometricManager.canAuthenticate(BIOMETRIC_STRONG or DEVICE_CREDENTIAL)) {
- BIOMETRIC_SUCCESS -> {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ when (biometricManager.canAuthenticate(BIOMETRIC_STRONG or DEVICE_CREDENTIAL)) {
+ BIOMETRIC_SUCCESS -> {
+ settingsManager.setBiometricLockEnabled(requireContext(), true)
+ return true
+ }
+
+ BIOMETRIC_ERROR_NONE_ENROLLED -> {
+ Toast.makeText(
+ requireContext(),
+ getString(R.string.biometric_auth_unavailable),
+ Toast.LENGTH_SHORT,
+ ).show()
+
+ if (prompt) {
+ promptAuthEnrollment()
+ }
+ }
+
+ else -> Toast.makeText(
+ requireContext(),
+ getString(R.string.biometric_auth_unavailable),
+ Toast.LENGTH_SHORT,
+ ).show()
+ }
+ } else {
+ val keyguardManager = requireContext()
+ .getSystemService(KEYGUARD_SERVICE) as KeyguardManager
+ if (keyguardManager.isDeviceSecure) {
settingsManager.setBiometricLockEnabled(requireContext(), true)
return true
- }
-
- BIOMETRIC_ERROR_NONE_ENROLLED -> {
+ } else {
Toast.makeText(
requireContext(),
getString(R.string.biometric_auth_unavailable),
Toast.LENGTH_SHORT,
).show()
- // Prompt the user to enroll valid credentials
- if (prompt && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
- val intent = Intent(ACTION_BIOMETRIC_ENROLL).apply {
- putExtra(
- EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED,
- BIOMETRIC_STRONG or DEVICE_CREDENTIAL
- )
- }
- biometricEnrollLauncher.launch(intent)
+ if (prompt) {
+ promptAuthEnrollment()
}
}
-
- else -> Toast.makeText(
- requireContext(),
- getString(R.string.biometric_auth_unavailable),
- Toast.LENGTH_SHORT,
- ).show()
}
return false
}
+ /**
+ * Prompt the user to enroll valid credentials
+ */
+ private fun promptAuthEnrollment() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ val intent = Intent(ACTION_BIOMETRIC_ENROLL).apply {
+ putExtra(
+ EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED,
+ BIOMETRIC_STRONG or DEVICE_CREDENTIAL
+ )
+ }
+ biometricEnrollLauncher.launch(intent)
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ val intent = Intent(ACTION_FINGERPRINT_ENROLL)
+ biometricEnrollLauncher.launch(intent)
+ } else {
+ val intent = Intent(ACTION_SECURITY_SETTINGS)
+ biometricEnrollLauncher.launch(intent)
+ }
+ }
+
private fun disableBiometrics() {
settingsManager.setBiometricLockEnabled(requireContext(), false)
}