diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/UnavailableFeatureDialogFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/UnavailableFeatureDialogFragment.kt index b866d72fe..9632021cd 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/UnavailableFeatureDialogFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/UnavailableFeatureDialogFragment.kt @@ -90,12 +90,12 @@ class UnavailableFeatureDialogFragment : DialogFragment() { } } if (apiName.isEmpty()) { - val mapper = arrayOf("ANDROID BASE", "ANDROID BASE 1.1", "CUPCAKE", "DONUT", "ECLAIR", "ECLAIR_0_1", "ECLAIR_MR1", "FROYO", "GINGERBREAD", "GINGERBREAD_MR1", "HONEYCOMB", "HONEYCOMB_MR1", "HONEYCOMB_MR2", "ICE_CREAM_SANDWICH", "ICE_CREAM_SANDWICH_MR1", "JELLY_BEAN", "JELLY_BEAN", "JELLY_BEAN", "KITKAT", "KITKAT", "LOLLIPOOP", "LOLLIPOOP_MR1", "MARSHMALLOW", "NOUGAT", "NOUGAT", "OREO", "OREO") + val mapper = arrayOf("ANDROID BASE", "ANDROID BASE 1.1", "CUPCAKE", "DONUT", "ECLAIR", "ECLAIR_0_1", "ECLAIR_MR1", "FROYO", "GINGERBREAD", "GINGERBREAD_MR1", "HONEYCOMB", "HONEYCOMB_MR1", "HONEYCOMB_MR2", "ICE_CREAM_SANDWICH", "ICE_CREAM_SANDWICH_MR1", "JELLY_BEAN", "JELLY_BEAN", "JELLY_BEAN", "KITKAT", "KITKAT", "LOLLIPOOP", "LOLLIPOOP_MR1", "MARSHMALLOW", "NOUGAT", "NOUGAT", "OREO", "OREO", "PIE", "", "") val index = apiNumber - 1 apiName = if (index < mapper.size) mapper[index] else "UNKNOWN_VERSION" } if (version.isEmpty()) { - val versions = arrayOf("1.0", "1.1", "1.5", "1.6", "2.0", "2.0.1", "2.1", "2.2.X", "2.3", "2.3.3", "3.0", "3.1", "3.2.0", "4.0.1", "4.0.3", "4.1.0", "4.2.0", "4.3.0", "4.4", "4.4", "5.0", "5.1", "6.0", "7.0", "7.1", "8.0.0", "8.1.0") + val versions = arrayOf("1.0", "1.1", "1.5", "1.6", "2.0", "2.0.1", "2.1", "2.2.X", "2.3", "2.3.3", "3.0", "3.1", "3.2.0", "4.0.1", "4.0.3", "4.1.0", "4.2.0", "4.3.0", "4.4", "4.4", "5.0", "5.1", "6.0", "7.0", "7.1", "8.0.0", "8.1.0", "9", "10", "11") val index = apiNumber - 1 version = if (index < versions.size) versions[index] else "UNKNOWN_VERSION" } diff --git a/app/src/main/java/com/kunzisoft/keepass/biometric/BiometricUnlockDatabaseHelper.kt b/app/src/main/java/com/kunzisoft/keepass/biometric/BiometricUnlockDatabaseHelper.kt index 7752ff822..bde6cf062 100644 --- a/app/src/main/java/com/kunzisoft/keepass/biometric/BiometricUnlockDatabaseHelper.kt +++ b/app/src/main/java/com/kunzisoft/keepass/biometric/BiometricUnlockDatabaseHelper.kt @@ -307,21 +307,26 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) { private const val BIOMETRIC_BLOCKS_MODES = KeyProperties.BLOCK_MODE_CBC private const val BIOMETRIC_ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7 + @RequiresApi(api = Build.VERSION_CODES.M) fun canAuthenticate(context: Context): Int { return try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - BiometricManager.from(context).canAuthenticate(BIOMETRIC_STRONG or DEVICE_CREDENTIAL) - } else { - BiometricManager.from(context).canAuthenticate(BIOMETRIC_STRONG) - } + BiometricManager.from(context).canAuthenticate( + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + BIOMETRIC_STRONG or DEVICE_CREDENTIAL + } else { + BIOMETRIC_STRONG + } + ) } catch (e: Exception) { Log.e(TAG, "Unable to authenticate with strong biometric.", e) try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - BiometricManager.from(context).canAuthenticate(BIOMETRIC_WEAK or DEVICE_CREDENTIAL) - } else { - BiometricManager.from(context).canAuthenticate(BIOMETRIC_WEAK) - } + BiometricManager.from(context).canAuthenticate( + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + BIOMETRIC_WEAK or DEVICE_CREDENTIAL + } else { + BIOMETRIC_WEAK + } + ) } catch (e: Exception) { Log.e(TAG, "Unable to authenticate with weak biometric.", e) BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE @@ -329,6 +334,7 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) { } } + @RequiresApi(api = Build.VERSION_CODES.M) fun allowInitKeyStore(context: Context): Boolean { val biometricCanAuthenticate = canAuthenticate(context) return ( biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS @@ -336,19 +342,42 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) { ) } - fun unlockSupported(context: Context): Boolean { - val biometricCanAuthenticate = canAuthenticate(context) - return ( biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS + @RequiresApi(api = Build.VERSION_CODES.M) + fun biometricUnlockSupported(context: Context): Boolean { + val biometricCanAuthenticate = try { + BiometricManager.from(context).canAuthenticate(BIOMETRIC_STRONG) + } catch (e: Exception) { + Log.e(TAG, "Unable to authenticate with strong biometric.", e) + try { + BiometricManager.from(context).canAuthenticate(BIOMETRIC_WEAK) + } catch (e: Exception) { + Log.e(TAG, "Unable to authenticate with weak biometric.", e) + BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE + } + } + return (biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS || biometricCanAuthenticate == BiometricManager.BIOMETRIC_STATUS_UNKNOWN || biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE || biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED || biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED - ) + ) + } + + @RequiresApi(api = Build.VERSION_CODES.R) + fun deviceCredentialUnlockSupported(context: Context): Boolean { + val biometricCanAuthenticate = BiometricManager.from(context).canAuthenticate(DEVICE_CREDENTIAL) + return (biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS + || biometricCanAuthenticate == BiometricManager.BIOMETRIC_STATUS_UNKNOWN + || biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE + || biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED + || biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED + ) } /** * Remove entry key in keystore */ + @RequiresApi(api = Build.VERSION_CODES.M) fun deleteEntryKeyInKeystoreForBiometric(context: FragmentActivity, biometricCallback: BiometricUnlockErrorCallback) { BiometricUnlockDatabaseHelper(context).apply { diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/NestedAppSettingsFragment.kt b/app/src/main/java/com/kunzisoft/keepass/settings/NestedAppSettingsFragment.kt index d2dc148fb..247e6d779 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/NestedAppSettingsFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/NestedAppSettingsFragment.kt @@ -30,7 +30,6 @@ import android.view.autofill.AutofillManager import android.widget.Toast import androidx.annotation.RequiresApi import androidx.appcompat.app.AlertDialog -import androidx.biometric.BiometricManager import androidx.preference.ListPreference import androidx.preference.Preference import androidx.preference.SwitchPreference @@ -209,14 +208,15 @@ class NestedAppSettingsFragment : NestedSettingsFragment() { activity?.let { activity -> val biometricUnlockEnablePreference: SwitchPreference? = findPreference(getString(R.string.biometric_unlock_enable_key)) - val deleteKeysFingerprints: Preference? = findPreference(getString(R.string.biometric_delete_all_key_key)) - // < M solve verifyError exception + val deviceCredentialUnlockEnablePreference: SwitchPreference? = findPreference(getString(R.string.device_credential_unlock_enable_key)) + val autoOpenPromptPreference: SwitchPreference? = findPreference(getString(R.string.biometric_auto_open_prompt_key)) + val biometricUnlockSupported = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - BiometricUnlockDatabaseHelper.unlockSupported(activity) + BiometricUnlockDatabaseHelper.biometricUnlockSupported(activity) } else false - if (!biometricUnlockSupported) { + biometricUnlockEnablePreference?.apply { // False if under Marshmallow - biometricUnlockEnablePreference?.apply { + if (!biometricUnlockSupported) { isChecked = false setOnPreferenceClickListener { preference -> (preference as SwitchPreference).isChecked = false @@ -224,9 +224,48 @@ class NestedAppSettingsFragment : NestedSettingsFragment() { .show(parentFragmentManager, "unavailableFeatureDialog") false } + } else { + setOnPreferenceChangeListener { _, newValue -> + val checked = (newValue as Boolean) + autoOpenPromptPreference?.isEnabled = checked + || deviceCredentialUnlockEnablePreference?.isChecked == true + if (checked) + deviceCredentialUnlockEnablePreference?.isChecked = false + true + } } - deleteKeysFingerprints?.isEnabled = false - } else { + } + + + val deviceCredentialUnlockSupported = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + BiometricUnlockDatabaseHelper.deviceCredentialUnlockSupported(activity) + } else false + deviceCredentialUnlockEnablePreference?.apply { + if (!deviceCredentialUnlockSupported) { + isChecked = false + setOnPreferenceClickListener { preference -> + (preference as SwitchPreference).isChecked = false + UnavailableFeatureDialogFragment.getInstance(Build.VERSION_CODES.R) + .show(parentFragmentManager, "unavailableFeatureDialog") + false + } + } else { + setOnPreferenceChangeListener { _, newValue -> + val checked = (newValue as Boolean) + autoOpenPromptPreference?.isEnabled = checked || + biometricUnlockEnablePreference?.isChecked == true + if (checked) + biometricUnlockEnablePreference?.isChecked = false + true + } + } + } + + autoOpenPromptPreference?.isEnabled = biometricUnlockEnablePreference?.isChecked == true + || deviceCredentialUnlockEnablePreference?.isChecked == true + + val deleteKeysFingerprints: Preference? = findPreference(getString(R.string.biometric_delete_all_key_key)) + if (biometricUnlockSupported || deviceCredentialUnlockSupported) { deleteKeysFingerprints?.setOnPreferenceClickListener { context?.let { context -> AlertDialog.Builder(context) @@ -260,6 +299,8 @@ class NestedAppSettingsFragment : NestedSettingsFragment() { } false } + } else { + deleteKeysFingerprints?.isEnabled = false } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1b34e09f2..34aa42078 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -324,8 +324,8 @@ Lets you scan your biometric to open the database Device credential unlocking Lets you use your device credential to open the database - Auto-open biometric prompt - Automatically ask for biometric if the database is set up to use it + Auto-open prompt + Automatically request advanced unlock if the database is set up to use it Delete encryption keys Delete all encryption keys related to advanced unlock recognition Delete all encryption keys related to advanced unlock recognition? diff --git a/app/src/main/res/xml/preferences_advanced_unlock.xml b/app/src/main/res/xml/preferences_advanced_unlock.xml index 618ff9b15..9ce2402fd 100644 --- a/app/src/main/res/xml/preferences_advanced_unlock.xml +++ b/app/src/main/res/xml/preferences_advanced_unlock.xml @@ -18,34 +18,27 @@ along with KeePassDX. If not, see . --> - + android:title="@string/general"> + - - - - - +