commit 1b02b10123de5b936afed0a6d3625c5b9faed9e1
parent e078ec7c1141334e2e1ebcee29e7f00b45c94aa3
Author: Iván Ávalos <avalos@disroot.org>
Date: Mon, 7 Jul 2025 16:15:52 +0200
[wallet] improvements to fingerprint lock
Diffstat:
2 files changed, 71 insertions(+), 5 deletions(-)
diff --git a/wallet/src/main/java/net/taler/wallet/settings/SettingsFragment.kt b/wallet/src/main/java/net/taler/wallet/settings/SettingsFragment.kt
@@ -16,11 +16,22 @@
package net.taler.wallet.settings
+import android.app.Activity.RESULT_OK
+import android.content.Intent
import android.os.Build
import android.os.Bundle
+import android.provider.Settings.ACTION_BIOMETRIC_ENROLL
+import android.provider.Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED
import android.view.View
-import androidx.activity.result.contract.ActivityResultContracts.OpenDocument
+import android.widget.Toast
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
+import androidx.activity.result.contract.ActivityResultContracts.OpenDocument
+import androidx.biometric.BiometricManager
+import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
+import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
+import androidx.biometric.BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
+import androidx.biometric.BiometricManager.BIOMETRIC_SUCCESS
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
@@ -29,7 +40,6 @@ import androidx.navigation.fragment.findNavController
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference
-import androidx.preference.SwitchPreferenceCompat
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_LONG
import com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_SHORT
@@ -51,6 +61,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
private val model: MainViewModel by activityViewModels()
private val settingsManager get() = model.settingsManager
private val withdrawManager by lazy { model.withdrawManager }
+ private lateinit var biometricManager: BiometricManager
private lateinit var prefDevMode: SwitchPreference
private lateinit var prefBiometricLock: SwitchPreference
@@ -78,6 +89,14 @@ class SettingsFragment : PreferenceFragmentCompat() {
)
}
+ private val biometricEnrollLauncher = registerForActivityResult(
+ ActivityResultContracts.StartActivityForResult(),
+ ) { result ->
+ if (result.resultCode == RESULT_OK) {
+ enableBiometrics(false)
+ }
+ }
+
private val logLauncher = registerForActivityResult(CreateDocument("text/plain")) { uri ->
settingsManager.exportLogcat(uri)
}
@@ -111,6 +130,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
+ biometricManager = BiometricManager.from(requireContext())
prefVersionApp.summary = "$VERSION_NAME ($FLAVOR $VERSION_CODE)"
prefVersionCore.summary = "${model.walletVersion} (${model.walletVersionHash?.take(7)})"
@@ -130,8 +150,13 @@ class SettingsFragment : PreferenceFragmentCompat() {
}
prefBiometricLock.setOnPreferenceChangeListener { _, newValue ->
- settingsManager.setBiometricLockEnabled(requireContext(), newValue as Boolean)
- true
+ val enabled = newValue as Boolean
+ if (enabled) {
+ return@setOnPreferenceChangeListener enableBiometrics(true)
+ } else {
+ disableBiometrics()
+ true
+ }
}
viewLifecycleOwner.lifecycleScope.launch {
@@ -201,6 +226,46 @@ class SettingsFragment : PreferenceFragmentCompat() {
requireActivity().title = getString(R.string.menu_settings)
}
+ private fun enableBiometrics(prompt: Boolean): Boolean {
+ 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()
+
+ // 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)
+ }
+ }
+
+ else -> Toast.makeText(
+ requireContext(),
+ getString(R.string.biometric_auth_unavailable),
+ Toast.LENGTH_SHORT,
+ ).show()
+ }
+
+ return false
+ }
+
+ private fun disableBiometrics() {
+ settingsManager.setBiometricLockEnabled(requireContext(), false)
+ }
+
private fun showImportDialog() {
MaterialAlertDialogBuilder(requireContext(), R.style.MaterialAlertDialog_Material3)
.setMessage(R.string.settings_dialog_import_message)
diff --git a/wallet/src/main/res/values/strings.xml b/wallet/src/main/res/values/strings.xml
@@ -78,6 +78,7 @@ GNU Taler is immune against many types of fraud, such as phishing of credit card
<!-- Biometric lock -->
<string name="biometric_auth_error">Authentication error: %1$s</string>
<string name="biometric_auth_failed">Authentication failed</string>
+ <string name="biometric_auth_unavailable">No authentication methods are available.</string>
<string name="biometric_prompt_title">Unlock to use wallet</string>
<string name="biometric_unlock_label">Tap to unlock</string>
@@ -404,7 +405,7 @@ GNU Taler is immune against many types of fraud, such as phishing of credit card
<string name="settings_dev_mode_summary">Shows more information intended for debugging</string>
<string name="settings_dialog_import_message">This operation will overwrite your existing database. Do you want to continue?</string>
<string name="settings_dialog_reset_message">Do you really want to reset the wallet and lose all coins and purchases?</string>
- <string name="settings_lock_auth">Lock with fingerprint</string>
+ <string name="settings_lock_auth">Protect access to wallet</string>
<string name="settings_lock_auth_summary">Require fingerprint or password to access the wallet</string>
<string name="settings_logcat">Debug log</string>
<string name="settings_logcat_error">Error exporting log</string>