taler-android

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

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:
Mwallet/src/main/java/net/taler/wallet/MainActivity.kt | 27++++++++++++++++++++-------
Mwallet/src/main/java/net/taler/wallet/settings/SettingsFragment.kt | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
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) }