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

View File

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

View File

@@ -62,12 +62,14 @@ class BiometricHelper(private val context: FragmentActivity, private val biometr
private val promptInfoStoreCredential = BiometricPrompt.PromptInfo.Builder() private val promptInfoStoreCredential = BiometricPrompt.PromptInfo.Builder()
.setTitle(context.getString(R.string.biometric_prompt_store_credential_title)) .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)) .setNegativeButtonText(context.getString(android.R.string.cancel))
.build() .build()
private val promptInfoExtractCredential = BiometricPrompt.PromptInfo.Builder() private val promptInfoExtractCredential = BiometricPrompt.PromptInfo.Builder()
.setTitle(context.getString(R.string.biometric_prompt_extract_credential_title)) .setTitle(context.getString(R.string.biometric_prompt_extract_credential_title))
.setDescription(context.getString(R.string.biometric_prompt_extract_credential_message))
//.setDeviceCredentialAllowed(true) //.setDeviceCredentialAllowed(true)
.setNegativeButtonText(context.getString(android.R.string.cancel)) .setNegativeButtonText(context.getString(android.R.string.cancel))
.build() .build()
@@ -294,8 +296,8 @@ class BiometricHelper(private val context: FragmentActivity, private val biometr
/** /**
* Remove entry key in keystore * Remove entry key in keystore
*/ */
fun deleteEntryKeyInKeystoreForFingerprints(context: FragmentActivity, fun deleteEntryKeyInKeystoreForBiometric(context: FragmentActivity,
biometricUnlockCallback: BiometricUnlockErrorCallback) { biometricUnlockCallback: BiometricUnlockErrorCallback) {
val fingerPrintHelper = BiometricHelper(context, object : BiometricUnlockCallback { val fingerPrintHelper = BiometricHelper(context, object : BiometricUnlockCallback {
override fun handleEncryptedResult(value: String, ivSpec: String) {} 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) .setPositiveButton(resources.getString(android.R.string.yes)
) { _, _ -> ) { _, _ ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
BiometricHelper.deleteEntryKeyInKeystoreForFingerprints( BiometricHelper.deleteEntryKeyInKeystoreForBiometric(
activity, activity,
object : BiometricHelper.BiometricUnlockErrorCallback { object : BiometricHelper.BiometricUnlockErrorCallback {
override fun onInvalidKeyException(e: Exception) {} override fun onInvalidKeyException(e: Exception) {}

View File

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

View File

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

View File

@@ -1,37 +1,55 @@
<?xml version="1.0" encoding="utf-8"?> <?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" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/fingerprint_container" android:id="@+id/fingerprint_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content">
tools:visibility="gone">
<View <View
android:id="@+id/biometric_delimiter"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1dp" android:layout_height="1dp"
android:background="?attr/colorPrimaryDark"/> android:background="?attr/colorPrimaryDark"
app:layout_constraintTop_toTopOf="parent"/>
<TextView <LinearLayout
android:id="@+id/fingerprint_label" android:layout_width="0dp"
android:layout_width="match_parent" android:layout_height="0dp"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="24dp" android:layout_marginStart="24dp"
android:layout_marginEnd="18dp" android:layout_marginEnd="18dp"
android:layout_toStartOf="@+id/fingerprint_image" android:orientation="vertical"
style="@style/KeepassDXStyle.TextAppearance.Default.TextOnPrimary" android:gravity="center_vertical"
android:gravity="center_vertical|start" /> 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 <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/fingerprint_image" android:id="@+id/biometric_image"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
android:layout_marginTop="@dimen/default_margin" android:layout_margin="@dimen/default_margin"
android:layout_marginBottom="@dimen/default_margin" app:layout_constraintTop_toTopOf="parent"
android:layout_marginStart="4dp" app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginEnd="@dimen/default_margin" app:layout_constraintEnd_toEndOf="parent"
android:layout_alignParentEnd="true"
android:elevation="8dp" android:elevation="8dp"
android:src="@drawable/fingerprint" android:src="@drawable/fingerprint"
android:background="@drawable/background_image" android:background="@drawable/background_image"
android:backgroundTint="?attr/colorAccent" /> 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="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_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="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_store_credential_title">Save biometric recognition</string>
<string name="biometric_prompt_extract_credential_title">Extract database credential with biometric data</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="encrypted_value_stored">Encrypted password stored</string>
<string name="biometric_invalid_key">Could not read biometric key. Restore your credential.</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> <string name="fingerprint_not_recognized">Could not recognize fingerprint</string>

View File

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