taler-android

Android apps for GNU Taler (wallet, PoS, cashier)
Log | Files | Refs | README | LICENSE

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:
Mwallet/src/main/java/net/taler/wallet/settings/SettingsFragment.kt | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mwallet/src/main/res/values/strings.xml | 3++-
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>