Better biometric view message implementation

This commit is contained in:
J-Jamet
2019-09-02 17:36:14 +02:00
parent 186ca30be8
commit 66988ecb66
9 changed files with 110 additions and 69 deletions

View File

@@ -29,7 +29,6 @@ import androidx.appcompat.app.AlertDialog
import android.view.View
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.biometric.FingerPrintAnimatedVector
import com.kunzisoft.keepass.settings.SettingsActivity
import com.kunzisoft.keepass.settings.SettingsAdvancedUnlockActivity
@RequiresApi(api = Build.VERSION_CODES.M)
@@ -53,7 +52,7 @@ class FingerPrintExplanationDialog : DialogFragment() {
}
fingerPrintAnimatedVector = FingerPrintAnimatedVector(activity,
rootView.findViewById(R.id.fingerprint_image))
rootView.findViewById(R.id.biometric_image))
builder.setView(rootView)
.setPositiveButton(android.R.string.ok) { _, _ -> }

View File

@@ -114,13 +114,13 @@ class AdvancedUnlockedViewManager(var context: FragmentActivity,
override fun onAuthenticationError(
errorCode: Int,
errString: CharSequence) {
Log.e(TAG, "Fingerprint authentication error. Code : $errorCode Error : $errString")
setAdvancedUnlockedView(errString.toString())
Log.e(TAG, "Biometric authentication error. Code : $errorCode Error : $errString")
setAdvancedUnlockedMessageView(errString.toString())
}
override fun onAuthenticationFailed() {
Log.e(TAG, "Fingerprint authentication failed, fingerprint not recognized")
setAdvancedUnlockedView(R.string.fingerprint_not_recognized)
Log.e(TAG, "Biometric authentication failed, biometric not recognized")
setAdvancedUnlockedMessageView(R.string.fingerprint_not_recognized)
}
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
@@ -155,21 +155,21 @@ class AdvancedUnlockedViewManager(var context: FragmentActivity,
private fun initNotConfigured() {
showFingerPrintViews(true)
setAdvancedUnlockedView(R.string.configure_biometric)
setAdvancedUnlockedTitleView(R.string.configure_biometric)
advancedUnlockInfoView?.setIconViewClickListener(null)
}
private fun initWaitData() {
showFingerPrintViews(true)
setAdvancedUnlockedView(R.string.no_credentials_stored)
setAdvancedUnlockedTitleView(R.string.no_credentials_stored)
advancedUnlockInfoView?.setIconViewClickListener(null)
}
private fun initEncryptData() {
showFingerPrintViews(true)
setAdvancedUnlockedView(R.string.open_biometric_prompt_store_credential)
setAdvancedUnlockedTitleView(R.string.open_biometric_prompt_store_credential)
biometricHelper?.initEncryptData { biometricPrompt, cryptoObject, promptInfo ->
@@ -185,7 +185,7 @@ class AdvancedUnlockedViewManager(var context: FragmentActivity,
private fun initDecryptData() {
showFingerPrintViews(true)
setAdvancedUnlockedView(R.string.open_biometric_prompt_unlock_database)
setAdvancedUnlockedTitleView(R.string.open_biometric_prompt_unlock_database)
if (biometricHelper != null) {
prefsNoBackup.getString(preferenceKeyIvSpec, null)?.let {
@@ -244,22 +244,6 @@ class AdvancedUnlockedViewManager(var context: FragmentActivity,
menuInflater.inflate(R.menu.advanced_unlock, menu)
}
private fun showFingerPrintViews(show: Boolean) {
context.runOnUiThread { advancedUnlockInfoView?.hide = !show }
}
private fun setAdvancedUnlockedView(textId: Int) {
context.runOnUiThread {
advancedUnlockInfoView?.setText(textId)
}
}
private fun setAdvancedUnlockedView(text: CharSequence) {
context.runOnUiThread {
advancedUnlockInfoView?.text = text
}
}
private fun removePrefsNoBackupKey() {
prefsNoBackup.edit()
?.remove(preferenceKeyValue)
@@ -267,6 +251,13 @@ class AdvancedUnlockedViewManager(var context: FragmentActivity,
?.apply()
}
fun deleteEntryKey() {
biometricHelper?.deleteEntryKey()
removePrefsNoBackupKey()
biometricMode = Mode.NOT_CONFIGURED
checkBiometricAvailability()
}
override fun handleEncryptedResult(
value: String,
ivSpec: String) {
@@ -275,7 +266,7 @@ class AdvancedUnlockedViewManager(var context: FragmentActivity,
?.putString(preferenceKeyIvSpec, ivSpec)
?.apply()
loadDatabase.invoke(null)
setAdvancedUnlockedView(R.string.encrypted_value_stored)
setAdvancedUnlockedMessageView(R.string.encrypted_value_stored)
}
override fun handleDecryptedResult(value: String) {
@@ -284,22 +275,36 @@ class AdvancedUnlockedViewManager(var context: FragmentActivity,
}
override fun onInvalidKeyException(e: Exception) {
setAdvancedUnlockedView(R.string.biometric_invalid_key)
deleteEntryKey()
setAdvancedUnlockedMessageView(R.string.biometric_invalid_key)
}
override fun onBiometricException(e: Exception) {
// Don't show error here;
// showError(getString(R.string.fingerprint_error, e.getMessage()));
// Can be uninit in Activity and init in fragment
setAdvancedUnlockedView(e.localizedMessage)
setAdvancedUnlockedMessageView(e.localizedMessage)
}
fun deleteEntryKey() {
biometricHelper?.deleteEntryKey()
removePrefsNoBackupKey()
biometricMode = Mode.NOT_CONFIGURED
checkBiometricAvailability()
private fun showFingerPrintViews(show: Boolean) {
context.runOnUiThread { advancedUnlockInfoView?.hide = !show }
}
private fun setAdvancedUnlockedTitleView(textId: Int) {
context.runOnUiThread {
advancedUnlockInfoView?.setTitle(textId)
}
}
private fun setAdvancedUnlockedMessageView(textId: Int) {
context.runOnUiThread {
advancedUnlockInfoView?.setMessage(textId)
}
}
private fun setAdvancedUnlockedMessageView(text: CharSequence) {
context.runOnUiThread {
advancedUnlockInfoView?.message = text
}
}
enum class Mode {

View File

@@ -62,12 +62,14 @@ class BiometricHelper(private val context: FragmentActivity, private val biometr
private val promptInfoStoreCredential = BiometricPrompt.PromptInfo.Builder()
.setTitle(context.getString(R.string.biometric_prompt_store_credential_title))
//.setDeviceCredentialAllowed(true)
.setDescription(context.getString(R.string.biometric_prompt_store_credential_message))
//.setDeviceCredentialAllowed(true) TODO device credential
.setNegativeButtonText(context.getString(android.R.string.cancel))
.build()
private val promptInfoExtractCredential = BiometricPrompt.PromptInfo.Builder()
.setTitle(context.getString(R.string.biometric_prompt_extract_credential_title))
.setDescription(context.getString(R.string.biometric_prompt_extract_credential_message))
//.setDeviceCredentialAllowed(true)
.setNegativeButtonText(context.getString(android.R.string.cancel))
.build()
@@ -294,8 +296,8 @@ class BiometricHelper(private val context: FragmentActivity, private val biometr
/**
* Remove entry key in keystore
*/
fun deleteEntryKeyInKeystoreForFingerprints(context: FragmentActivity,
biometricUnlockCallback: BiometricUnlockErrorCallback) {
fun deleteEntryKeyInKeystoreForBiometric(context: FragmentActivity,
biometricUnlockCallback: BiometricUnlockErrorCallback) {
val fingerPrintHelper = BiometricHelper(context, object : BiometricUnlockCallback {
override fun handleEncryptedResult(value: String, ivSpec: String) {}

View File

@@ -252,7 +252,7 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen
.setPositiveButton(resources.getString(android.R.string.yes)
) { _, _ ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
BiometricHelper.deleteEntryKeyInKeystoreForFingerprints(
BiometricHelper.deleteEntryKeyInKeystoreForBiometric(
activity,
object : BiometricHelper.BiometricUnlockErrorCallback {
override fun onInvalidKeyException(e: Exception) {}

View File

@@ -19,7 +19,8 @@ class AdvancedUnlockInfoView @JvmOverloads constructor(context: Context,
private val unlockContainerView: View
private var unlockAnimatedVector: FingerPrintAnimatedVector? = null
private var unlockTextView: TextView? = null
private var unlockTitleTextView: TextView? = null
private var unlockMessageTextView: TextView? = null
var unlockIconImageView: ImageView? = null
init {
@@ -30,8 +31,9 @@ class AdvancedUnlockInfoView @JvmOverloads constructor(context: Context,
unlockContainerView = findViewById(R.id.fingerprint_container)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
unlockTextView = findViewById(R.id.fingerprint_label)
unlockIconImageView = findViewById(R.id.fingerprint_image)
unlockTitleTextView = findViewById(R.id.biometric_title)
unlockMessageTextView = findViewById(R.id.biometric_message)
unlockIconImageView = findViewById(R.id.biometric_image)
// Init the fingerprint animation
unlockAnimatedVector = FingerPrintAnimatedVector(context, unlockIconImageView!!)
}
@@ -58,16 +60,28 @@ class AdvancedUnlockInfoView @JvmOverloads constructor(context: Context,
unlockIconImageView?.setOnClickListener(listener)
}
var text: CharSequence
var title: CharSequence
get() {
return unlockTextView?.text?.toString() ?: ""
return unlockTitleTextView?.text?.toString() ?: ""
}
set(value) {
unlockTextView?.text = value
unlockTitleTextView?.text = value
}
fun setText(@StringRes textId: Int) {
text = context.getString(textId)
fun setTitle(@StringRes textId: Int) {
title = context.getString(textId)
}
var message: CharSequence
get() {
return unlockMessageTextView?.text?.toString() ?: ""
}
set(value) {
unlockMessageTextView?.text = value
}
fun setMessage(@StringRes textId: Int) {
message = context.getString(textId)
}
var hide: Boolean

View File

@@ -98,7 +98,7 @@
android:text="@string/fingerprint_open_biometric_prompt"/>
</LinearLayout>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/fingerprint_image"
android:id="@+id/biometric_image"
android:layout_gravity="center"
android:layout_width="48dp"
android:layout_height="48dp"

View File

@@ -1,37 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/fingerprint_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:visibility="gone">
android:layout_height="wrap_content">
<View
android:id="@+id/biometric_delimiter"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/colorPrimaryDark"/>
android:background="?attr/colorPrimaryDark"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:id="@+id/fingerprint_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
<LinearLayout
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="24dp"
android:layout_marginEnd="18dp"
android:layout_toStartOf="@+id/fingerprint_image"
style="@style/KeepassDXStyle.TextAppearance.Default.TextOnPrimary"
android:gravity="center_vertical|start" />
android:orientation="vertical"
android:gravity="center_vertical"
app:layout_constraintTop_toTopOf="@+id/biometric_delimiter"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/biometric_image" >
<TextView
android:id="@+id/biometric_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="@string/biometric_prompt_store_credential_title"
style="@style/KeepassDXStyle.TextAppearance.Default.TextOnPrimary"
android:gravity="center_vertical|end" />
<TextView
android:id="@+id/biometric_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="Sample error"
style="@style/KeepassDXStyle.TextAppearance.Secondary.TextOnPrimary"
android:gravity="center_vertical|end" />
</LinearLayout>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/fingerprint_image"
android:id="@+id/biometric_image"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginTop="@dimen/default_margin"
android:layout_marginBottom="@dimen/default_margin"
android:layout_marginStart="4dp"
android:layout_marginEnd="@dimen/default_margin"
android:layout_alignParentEnd="true"
android:layout_margin="@dimen/default_margin"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:elevation="8dp"
android:src="@drawable/fingerprint"
android:background="@drawable/background_image"
android:backgroundTint="?attr/colorAccent" />
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -229,8 +229,10 @@
<string name="configure_biometric">Biometric prompt is supported but not set up.</string>
<string name="open_biometric_prompt_unlock_database">Open the biometric prompt to unlock the database</string>
<string name="open_biometric_prompt_store_credential">Open the biometric prompt to store credentials</string>
<string name="biometric_prompt_store_credential_title">Store database credentials with biometric data</string>
<string name="biometric_prompt_extract_credential_title">Extract database credential with biometric data</string>
<string name="biometric_prompt_store_credential_title">Save biometric recognition</string>
<string name="biometric_prompt_store_credential_message">Store database credentials with biometric data</string>
<string name="biometric_prompt_extract_credential_title">Open database with biometric recognition</string>
<string name="biometric_prompt_extract_credential_message">Extract database credential with biometric data</string>
<string name="encrypted_value_stored">Encrypted password stored</string>
<string name="biometric_invalid_key">Could not read biometric key. Restore your credential.</string>
<string name="fingerprint_not_recognized">Could not recognize fingerprint</string>

View File

@@ -226,6 +226,7 @@
<item name="android:textColor">?attr/textColorInverse</item>
</style>
<style name="KeepassDXStyle.TextAppearance.Secondary.TextOnPrimary" parent="KeepassDXStyle.TextAppearance.Default.TextOnPrimary">
<item name="android:textColor">?android:attr/textColorHintInverse</item>
<item name="android:textSize">14sp</item>
<item name="android:textStyle">italic</item>
</style>