diff --git a/src/keepass2android/FingerprintModule.cs b/src/keepass2android/FingerprintModule.cs
new file mode 100644
index 00000000..6abac0c9
--- /dev/null
+++ b/src/keepass2android/FingerprintModule.cs
@@ -0,0 +1,434 @@
+using System;
+using Android.Content;
+using Javax.Crypto;
+using Java.Security;
+using Java.Lang;
+using Android.Views.InputMethods;
+using Android.App;
+using Android.Hardware.Fingerprints;
+using Android.OS;
+using Android.Security.Keystore;
+using Android.Preferences;
+using Android.Util;
+using Android.Widget;
+using Java.IO;
+using Java.Security.Cert;
+using Javax.Crypto.Spec;
+
+namespace keepass2android
+{
+ public class FingerprintModule
+ {
+ public Context Context { get; set; }
+
+ public FingerprintModule (Context context)
+ {
+ Context = context;
+ }
+
+ public FingerprintManager FingerprintManager
+ {
+ get { return (FingerprintManager) Context.GetSystemService(Context.FingerprintService); }
+ }
+
+ public KeyguardManager KeyguardManager
+ {
+ get
+ {
+ return (KeyguardManager) Context.GetSystemService("keyguard");
+ }
+ }
+
+
+ public KeyStore Keystore
+ {
+ get
+ {
+ try
+ {
+ return KeyStore.GetInstance("AndroidKeyStore");
+ }
+ catch (KeyStoreException e)
+ {
+ throw new RuntimeException("Failed to get an instance of KeyStore", e);
+ }
+ }
+ }
+
+ public KeyGenerator KeyGenerator
+ {
+ get
+ {
+ try
+ {
+ return KeyGenerator.GetInstance(KeyProperties.KeyAlgorithmAes, "AndroidKeyStore");
+ }
+ catch (NoSuchAlgorithmException e)
+ {
+ throw new RuntimeException("Failed to get an instance of KeyGenerator", e);
+ }
+ catch (NoSuchProviderException e)
+ {
+ throw new RuntimeException("Failed to get an instance of KeyGenerator", e);
+ }
+ }
+ }
+
+ public Cipher Cipher
+ {
+ get
+ {
+ try
+ {
+ return Cipher.GetInstance(KeyProperties.KeyAlgorithmAes + "/"
+ + KeyProperties.BlockModeCbc + "/"
+ + KeyProperties.EncryptionPaddingPkcs7);
+ }
+ catch (NoSuchAlgorithmException e)
+ {
+ throw new RuntimeException("Failed to get an instance of Cipher", e);
+ }
+ catch (NoSuchPaddingException e)
+ {
+ throw new RuntimeException("Failed to get an instance of Cipher", e);
+ }
+ }
+ }
+
+ public InputMethodManager InputMethodManager
+ {
+ get { return (InputMethodManager) Context.GetSystemService(Context.InputMethodService); }
+ }
+
+ public ISharedPreferences SharedPreferences
+ {
+ get { return PreferenceManager.GetDefaultSharedPreferences(Context); }
+ }
+ }
+
+ public abstract class FingerprintCrypt: FingerprintManager.AuthenticationCallback
+ {
+ protected const string FailedToInitCipher = "Failed to init Cipher";
+ public override void OnAuthenticationError(FingerprintState errorCode, ICharSequence errString)
+ {
+ Kp2aLog.Log("FP: OnAuthenticationError: " + errString + ", " + _selfCancelled);
+ if (!_selfCancelled)
+ _callback.OnAuthenticationError(errorCode, errString);
+ }
+
+ public override void OnAuthenticationFailed()
+ {
+ Kp2aLog.Log("FP: OnAuthenticationFailed " + _selfCancelled);
+ _callback.OnAuthenticationFailed();
+ }
+
+ public override void OnAuthenticationHelp(FingerprintState helpCode, ICharSequence helpString)
+ {
+ _callback.OnAuthenticationHelp(helpCode, helpString);
+ }
+
+ public override void OnAuthenticationSucceeded(FingerprintManager.AuthenticationResult result)
+ {
+ Kp2aLog.Log("FP: OnAuthenticationSucceeded ");
+ StopListening();
+ _callback.OnAuthenticationSucceeded(result);
+ }
+
+ protected readonly string _keyId;
+
+ protected Cipher _cipher;
+ private bool _selfCancelled;
+ private CancellationSignal _cancellationSignal;
+ protected FingerprintManager.CryptoObject _cryptoObject;
+ private FingerprintManager.AuthenticationCallback _callback;
+ protected KeyStore _keystore;
+
+ private FingerprintManager _fingerprintManager;
+
+ public FingerprintCrypt(FingerprintModule fingerprint, string keyId)
+ {
+ Kp2aLog.Log("FP: Create " + this.GetType().Name);
+ _keyId = keyId;
+
+ _cipher = fingerprint.Cipher;
+ _keystore = fingerprint.Keystore;
+
+ _fingerprintManager = fingerprint.FingerprintManager;
+
+ }
+
+ public abstract bool InitCipher();
+ protected static string GetAlias(string keyId)
+ {
+ return "keepass2android." + keyId;
+ }
+ protected static string GetIvPrefKey(string prefKey)
+ {
+ return prefKey + "_iv";
+ }
+ public bool IsFingerprintAuthAvailable
+ {
+ get
+ {
+ return _fingerprintManager.IsHardwareDetected
+ && _fingerprintManager.HasEnrolledFingerprints;
+ }
+ }
+
+ public void StartListening(FingerprintManager.AuthenticationCallback callback)
+ {
+ if (!IsFingerprintAuthAvailable)
+ return;
+
+ Kp2aLog.Log("FP: StartListening ");
+ _cancellationSignal = new CancellationSignal();
+ _selfCancelled = false;
+ _callback = callback;
+ _fingerprintManager.Authenticate(_cryptoObject, _cancellationSignal, 0 /* flags */, this, null);
+
+ }
+
+ public void StopListening()
+ {
+ if (_cancellationSignal != null)
+ {
+ Kp2aLog.Log("FP: StopListening ");
+ _selfCancelled = true;
+ _cancellationSignal.Cancel();
+ _cancellationSignal = null;
+ }
+ }
+
+ public string Encrypt(string textToEncrypt)
+ {
+ Kp2aLog.Log("FP: Encrypting");
+ return Base64.EncodeToString(_cipher.DoFinal(System.Text.Encoding.UTF8.GetBytes(textToEncrypt)), 0);
+ }
+
+
+ public void StoreEncrypted(string textToEncrypt, string prefKey, Context context)
+ {
+ var edit = PreferenceManager.GetDefaultSharedPreferences(context).Edit();
+ StoreEncrypted(textToEncrypt, prefKey, edit);
+ edit.Commit();
+ }
+
+ public void StoreEncrypted(string textToEncrypt, string prefKey, ISharedPreferencesEditor edit)
+ {
+ edit.PutString(prefKey, Encrypt(textToEncrypt));
+ edit.PutString(GetIvPrefKey(prefKey), Base64.EncodeToString(CipherIv, 0));
+
+ }
+
+
+ private byte[] CipherIv
+ {
+ get { return _cipher.GetIV(); }
+ }
+ }
+
+ public class FingerprintDecryption : FingerprintCrypt
+ {
+ private readonly Context _context;
+ private readonly byte[] _iv;
+
+
+ public FingerprintDecryption(FingerprintModule fingerprint, string keyId, byte[] iv) : base(fingerprint, keyId)
+ {
+ _iv = iv;
+ }
+
+ public FingerprintDecryption(FingerprintModule fingerprint, string keyId, Context context, string prefKey)
+ : base(fingerprint, keyId)
+ {
+ _context = context;
+ _iv = Base64.Decode(PreferenceManager.GetDefaultSharedPreferences(context).GetString(GetIvPrefKey(prefKey), null), 0);
+ }
+
+ public override bool InitCipher()
+ {
+ Kp2aLog.Log("FP: InitCipher for Dec");
+ try
+ {
+ _keystore.Load(null);
+ var key = _keystore.GetKey(GetAlias(_keyId), null);
+ var ivParams = new IvParameterSpec(_iv);
+ _cipher.Init(CipherMode.DecryptMode, key, ivParams);
+
+ _cryptoObject = new FingerprintManager.CryptoObject(_cipher);
+ return true;
+ }
+ catch (KeyPermanentlyInvalidatedException)
+ {
+ return false;
+ }
+ catch (KeyStoreException e)
+ {
+ throw new RuntimeException(FailedToInitCipher, e);
+ }
+ catch (CertificateException e)
+ {
+ throw new RuntimeException(FailedToInitCipher, e);
+ }
+ catch (UnrecoverableKeyException e)
+ {
+ throw new RuntimeException(FailedToInitCipher, e);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(FailedToInitCipher, e);
+ }
+ catch (NoSuchAlgorithmException e)
+ {
+ throw new RuntimeException(FailedToInitCipher, e);
+ }
+ catch (InvalidKeyException e)
+ {
+ throw new RuntimeException(FailedToInitCipher, e);
+ }
+ }
+
+
+ public string Decrypt(string encryted)
+ {
+ Kp2aLog.Log("FP: Decrypting ");
+ byte[] encryptedBytes = Base64.Decode(encryted, 0);
+ return System.Text.Encoding.UTF8.GetString(_cipher.DoFinal(encryptedBytes));
+ }
+
+ public string DecryptStored(string prefKey)
+ {
+ string enc = PreferenceManager.GetDefaultSharedPreferences(_context).GetString(prefKey, null);
+ return Decrypt(enc);
+ }
+ }
+
+ public class FingerprintEncryption : FingerprintCrypt
+ {
+
+ private KeyGenerator _keyGen;
+
+
+ public FingerprintEncryption(FingerprintModule fingerprint, string keyId) :
+ base(fingerprint, keyId)
+ {
+ _keyGen = fingerprint.KeyGenerator;
+ Kp2aLog.Log("FP: CreateKey ");
+ CreateKey();
+ }
+
+
+ ///
+ /// Creates a symmetric key in the Android Key Store which can only be used after the user
+ /// has authenticated with fingerprint.
+ ///
+ private void CreateKey()
+ {
+ try
+ {
+ _keystore.Load(null);
+ _keyGen.Init(new KeyGenParameterSpec.Builder(GetAlias(_keyId),
+ KeyStorePurpose.Encrypt | KeyStorePurpose.Decrypt)
+ .SetBlockModes(KeyProperties.BlockModeCbc)
+ // Require the user to authenticate with a fingerprint to authorize every use
+ // of the key
+ .SetUserAuthenticationRequired(true)
+ .SetEncryptionPaddings(KeyProperties.EncryptionPaddingPkcs7)
+ .Build());
+ _keyGen.GenerateKey();
+ }
+ catch (NoSuchAlgorithmException e)
+ {
+ throw new RuntimeException(e);
+ }
+ catch (InvalidAlgorithmParameterException e)
+ {
+ throw new RuntimeException(e);
+ }
+ catch (CertificateException e)
+ {
+ throw new RuntimeException(e);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public override bool InitCipher()
+ {
+ Kp2aLog.Log("FP: InitCipher for Enc ");
+ try
+ {
+ _keystore.Load(null);
+ var key = _keystore.GetKey(GetAlias(_keyId), null);
+ _cipher.Init(CipherMode.EncryptMode, key);
+
+ _cryptoObject = new FingerprintManager.CryptoObject(_cipher);
+ return true;
+ }
+ catch (KeyPermanentlyInvalidatedException)
+ {
+ return false;
+ }
+ catch (KeyStoreException e)
+ {
+ throw new RuntimeException(FailedToInitCipher, e);
+ }
+ catch (CertificateException e)
+ {
+ throw new RuntimeException(FailedToInitCipher, e);
+ }
+ catch (UnrecoverableKeyException e)
+ {
+ throw new RuntimeException(FailedToInitCipher, e);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(FailedToInitCipher, e);
+ }
+ catch (NoSuchAlgorithmException e)
+ {
+ throw new RuntimeException(FailedToInitCipher, e);
+ }
+ catch (InvalidKeyException e)
+ {
+ throw new RuntimeException(FailedToInitCipher, e);
+ }
+ }
+ }
+
+ public class FingerprintAuthCallbackAdapter : FingerprintManager.AuthenticationCallback
+ {
+ private readonly IFingerprintAuthCallback _callback;
+ private readonly Context _context;
+
+ public FingerprintAuthCallbackAdapter(IFingerprintAuthCallback callback, Context context)
+ {
+ _callback = callback;
+ _context = context;
+ }
+
+ public override void OnAuthenticationSucceeded(FingerprintManager.AuthenticationResult result)
+ {
+ _callback.OnFingerprintAuthSucceeded();
+ }
+
+ public override void OnAuthenticationError(FingerprintState errorCode, ICharSequence errString)
+ {
+ _callback.OnFingerprintError(errString.ToString());
+ }
+
+ public override void OnAuthenticationHelp(FingerprintState helpCode, ICharSequence helpString)
+ {
+ _callback.OnFingerprintError(helpString.ToString());
+ }
+
+ public override void OnAuthenticationFailed()
+ {
+ _callback.OnFingerprintError(
+ _context.Resources.GetString(Resource.String.fingerprint_not_recognized));
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/keepass2android/FingerprintSetupActivity.cs b/src/keepass2android/FingerprintSetupActivity.cs
new file mode 100644
index 00000000..aaef9ed7
--- /dev/null
+++ b/src/keepass2android/FingerprintSetupActivity.cs
@@ -0,0 +1,313 @@
+using System;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using Android;
+using Android.App;
+using Android.Content;
+using Android.Content.PM;
+using Android.Hardware.Fingerprints;
+using Android.OS;
+using Android.Preferences;
+using Android.Runtime;
+using Android.Views;
+using Android.Widget;
+using Java.Lang;
+using KeePassLib.Keys;
+using KeePassLib.Utility;
+using Enum = System.Enum;
+using Exception = System.Exception;
+
+namespace keepass2android
+{
+ [Activity(Label = "@string/app_name",
+ ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.KeyboardHidden,
+ Theme = "@style/MyTheme_ActionBar", MainLauncher = false)]
+ [IntentFilter(new[] { "kp2a.action.FingerprintSetupActivity" }, Categories = new[] { Intent.CategoryDefault })]
+ public class FingerprintSetupActivity : LockCloseActivity
+ {
+ private readonly ActivityDesign _activityDesign;
+
+ public FingerprintSetupActivity(IntPtr javaReference, JniHandleOwnership transfer)
+ : base(javaReference, transfer)
+ {
+ _activityDesign = new ActivityDesign(this);
+ }
+ public FingerprintSetupActivity()
+ {
+ _activityDesign = new ActivityDesign(this);
+ }
+
+
+
+ private FingerprintUnlockMode _unlockMode = FingerprintUnlockMode.Disabled;
+ private FingerprintUnlockMode _desiredUnlockMode;
+ private FingerprintEncryption _enc;
+ private RadioButton[] _radioButtons;
+ public override bool OnOptionsItemSelected(IMenuItem item)
+ {
+ switch (item.ItemId)
+ {
+
+ case Android.Resource.Id.Home:
+ Finish();
+ return true;
+ }
+
+ return base.OnOptionsItemSelected(item);
+ }
+
+ protected override void OnCreate(Bundle savedInstanceState)
+ {
+ _activityDesign.ApplyTheme();
+ base.OnCreate(savedInstanceState);
+ SetContentView(Resource.Layout.fingerprint_setup);
+
+ Enum.TryParse(
+ PreferenceManager.GetDefaultSharedPreferences(this).GetString(App.Kp2a.GetDb().CurrentFingerprintModePrefKey, ""),
+ out _unlockMode);
+
+ _fpIcon = FindViewById(Resource.Id.fingerprint_icon);
+ _fpTextView = FindViewById(Resource.Id.fingerprint_status);
+
+ SupportActionBar.SetDisplayHomeAsUpEnabled(true);
+ SupportActionBar.SetHomeButtonEnabled(true);
+
+ int[] radioButtonIds =
+ {
+ Resource.Id.radio_fingerprint_quickunlock, Resource.Id.radio_fingerprint_unlock,
+ Resource.Id.radio_fingerprint_disabled
+ };
+ _radioButtons = radioButtonIds.Select(FindViewById).ToArray();
+ _radioButtons[0].Tag = FingerprintUnlockMode.QuickUnlock.ToString();
+ _radioButtons[1].Tag = FingerprintUnlockMode.FullUnlock.ToString();
+ _radioButtons[2].Tag = FingerprintUnlockMode.Disabled.ToString();
+ foreach (RadioButton r in _radioButtons)
+ {
+ r.CheckedChange += (sender, args) =>
+ {
+ var rbSender = ((RadioButton) sender);
+ if (!rbSender.Checked) return;
+ foreach (RadioButton rOther in _radioButtons)
+ {
+ if (rOther == sender) continue;
+ rOther.Checked = false;
+ }
+ FingerprintUnlockMode newMode;
+ Enum.TryParse(rbSender.Tag.ToString(), out newMode);
+ ChangeUnlockMode(_unlockMode, newMode);
+
+ };
+ }
+
+ CheckCurrentRadioButton();
+
+ int errorId = Resource.String.fingerprint_os_error;
+ SetError(errorId);
+
+ FindViewById(Resource.Id.cancel_button).Click += (sender, args) =>
+ {
+ _enc.StopListening();
+ _unlockMode = FingerprintUnlockMode.Disabled; //cancelling a FingerprintEncryption means a new key has been created but not been authenticated to encrypt something. We can't keep the previous state.
+ StoreUnlockMode();
+ FindViewById(Resource.Id.radio_buttons).Visibility = ViewStates.Visible;
+ FindViewById(Resource.Id.fingerprint_auth_container).Visibility = ViewStates.Gone;
+ _enc = null;
+ CheckCurrentRadioButton();
+ };
+
+ FindViewById(Resource.Id.radio_buttons).Visibility = ViewStates.Gone;
+ FindViewById(Resource.Id.fingerprint_auth_container).Visibility = ViewStates.Gone;
+
+ if ((int)Build.VERSION.SdkInt >= 23)
+ RequestPermissions(new[] { Manifest.Permission.UseFingerprint }, FingerprintPermissionRequestCode);
+ }
+
+ string CurrentPreferenceKey
+ {
+ get { return App.Kp2a.GetDb().CurrentFingerprintPrefKey; }
+ }
+
+ private void StoreUnlockMode()
+ {
+ ISharedPreferencesEditor edit = PreferenceManager.GetDefaultSharedPreferences(this).Edit();
+ if (_unlockMode == FingerprintUnlockMode.Disabled)
+ {
+ edit.PutString(CurrentPreferenceKey, "");
+ }
+ else
+ {
+ if (_unlockMode == FingerprintUnlockMode.FullUnlock)
+ _enc.StoreEncrypted(App.Kp2a.GetDb().KpDatabase.MasterKey.GetUserKey().Password.ReadString(), CurrentPreferenceKey, edit);
+ else
+ _enc.StoreEncrypted("QuickUnlock" /*some dummy data*/, CurrentPreferenceKey, edit);
+ }
+ edit.PutString(App.Kp2a.GetDb().CurrentFingerprintModePrefKey, _unlockMode.ToString());
+ edit.Commit();
+ }
+
+ private void CheckCurrentRadioButton()
+ {
+
+ foreach (RadioButton r in _radioButtons)
+ {
+ FingerprintUnlockMode um;
+ Enum.TryParse(r.Tag.ToString(), out um);
+ if (um == _unlockMode)
+ r.Checked = true;
+ }
+ }
+
+ private void SetError(int errorId)
+ {
+ var tv = FindViewById(Resource.Id.tvFatalError);
+ tv.Text = GetString(Resource.String.fingerprint_fatal) + " " + GetString(errorId);
+ tv.Visibility = ViewStates.Visible;
+ }
+
+ const int FingerprintPermissionRequestCode = 0;
+ public override void OnRequestPermissionsResult (int requestCode, string[] permissions, Permission[] grantResults)
+ {
+ if (requestCode == FingerprintPermissionRequestCode && grantResults[0] == Permission.Granted)
+ {
+ FingerprintModule fpModule = new FingerprintModule(this);
+ if (!fpModule.FingerprintManager.IsHardwareDetected)
+ {
+ SetError(Resource.String.fingerprint_hardware_error);
+ return;
+ }
+ if (!fpModule.FingerprintManager.HasEnrolledFingerprints)
+ {
+ SetError(Resource.String.fingerprint_no_enrolled);
+ return;
+ }
+ FindViewById(Resource.Id.tvFatalError).Visibility = ViewStates.Gone;
+ FindViewById(Resource.Id.radio_buttons).Visibility = ViewStates.Visible;
+ FindViewById(Resource.Id.fingerprint_auth_container).Visibility = ViewStates.Gone;
+ }
+ }
+
+
+ private void ChangeUnlockMode(FingerprintUnlockMode oldMode, FingerprintUnlockMode newMode)
+ {
+ if (oldMode == newMode)
+ return;
+
+ if (newMode == FingerprintUnlockMode.Disabled)
+ {
+ _unlockMode = newMode;
+ StoreUnlockMode();
+ return;
+ }
+
+ _desiredUnlockMode = newMode;
+ FindViewById(Resource.Id.radio_buttons).Visibility = ViewStates.Gone;
+ FindViewById(Resource.Id.fingerprint_auth_container).Visibility = ViewStates.Visible;
+
+ _enc = new FingerprintEncryption(new FingerprintModule(this), CurrentPreferenceKey);
+ try
+ {
+ if (!_enc.InitCipher())
+ throw new Exception("Failed to initialize cipher");
+ ResetErrorTextRunnable();
+ _enc.StartListening(new SetupCallback(this));
+ }
+ catch (Exception e)
+ {
+ CheckCurrentRadioButton();
+ Toast.MakeText(this, e.ToString(), ToastLength.Long).Show();
+ FindViewById(Resource.Id.radio_buttons).Visibility = ViewStates.Visible;
+ FindViewById(Resource.Id.fingerprint_auth_container).Visibility = ViewStates.Gone;
+ }
+
+
+ }
+
+ static readonly long ERROR_TIMEOUT_MILLIS = 1600;
+ static readonly long SUCCESS_DELAY_MILLIS = 1300;
+ private ImageView _fpIcon;
+ private TextView _fpTextView;
+ public void OnAuthSucceeded()
+ {
+ _unlockMode = _desiredUnlockMode;
+
+ _fpTextView.RemoveCallbacks(ResetErrorTextRunnable);
+ _fpIcon.SetImageResource(Resource.Drawable.ic_fingerprint_success);
+ _fpTextView.SetTextColor(_fpTextView.Resources.GetColor(Resource.Color.success_color, null));
+ _fpTextView.Text = _fpTextView.Resources.GetString(Resource.String.fingerprint_success);
+ _fpIcon.PostDelayed(() =>
+ {
+ FindViewById(Resource.Id.radio_buttons).Visibility = ViewStates.Visible;
+ FindViewById(Resource.Id.fingerprint_auth_container).Visibility = ViewStates.Gone;
+
+ StoreUnlockMode();
+
+ }, SUCCESS_DELAY_MILLIS);
+
+
+ }
+
+
+ public void OnFingerprintError(string error)
+ {
+ _fpIcon.SetImageResource(Resource.Drawable.ic_fingerprint_error);
+ _fpTextView.Text = error;
+ _fpTextView.SetTextColor(
+ _fpTextView.Resources.GetColor(Resource.Color.warning_color, null));
+ _fpTextView.RemoveCallbacks(ResetErrorTextRunnable);
+ _fpTextView.PostDelayed(ResetErrorTextRunnable, ERROR_TIMEOUT_MILLIS);
+ }
+
+ void ResetErrorTextRunnable()
+ {
+ _fpTextView.SetTextColor(
+ _fpTextView.Resources.GetColor(Resource.Color.hint_color, null));
+ _fpTextView.Text = _fpTextView.Resources.GetString(Resource.String.fingerprint_hint);
+ _fpIcon.SetImageResource(Resource.Drawable.ic_fp_40px);
+ }
+
+ protected override void OnResume()
+ {
+ base.OnResume();
+ if (_enc != null)
+ _enc.StartListening(new SetupCallback(this));
+ }
+
+ protected override void OnPause()
+ {
+ base.OnPause();
+ if (_enc != null)
+ _enc.StopListening();
+ }
+ }
+
+ internal class SetupCallback : FingerprintManager.AuthenticationCallback
+ {
+ private readonly FingerprintSetupActivity _fingerprintSetupActivity;
+
+ public SetupCallback(FingerprintSetupActivity fingerprintSetupActivity)
+ {
+ _fingerprintSetupActivity = fingerprintSetupActivity;
+ }
+
+ public override void OnAuthenticationSucceeded(FingerprintManager.AuthenticationResult result)
+ {
+ _fingerprintSetupActivity.OnAuthSucceeded();
+ }
+
+ public override void OnAuthenticationError(FingerprintState errorCode, ICharSequence errString)
+ {
+ _fingerprintSetupActivity.OnFingerprintError(errString.ToString());
+ }
+
+ public override void OnAuthenticationHelp(FingerprintState helpCode, ICharSequence helpString)
+ {
+ _fingerprintSetupActivity.OnFingerprintError(helpString.ToString());
+ }
+
+ public override void OnAuthenticationFailed()
+ {
+ _fingerprintSetupActivity.OnFingerprintError(_fingerprintSetupActivity.Resources.GetString(Resource.String.fingerprint_not_recognized));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/keepass2android/FingerprintUnlockMode.cs b/src/keepass2android/FingerprintUnlockMode.cs
new file mode 100644
index 00000000..bd80f262
--- /dev/null
+++ b/src/keepass2android/FingerprintUnlockMode.cs
@@ -0,0 +1,9 @@
+namespace keepass2android
+{
+ public enum FingerprintUnlockMode
+ {
+ Disabled = 0,
+ QuickUnlock = 1,
+ FullUnlock = 2,
+ }
+}
\ No newline at end of file
diff --git a/src/keepass2android/QuickUnlock.cs b/src/keepass2android/QuickUnlock.cs
index ce11d5b6..088c46ba 100644
--- a/src/keepass2android/QuickUnlock.cs
+++ b/src/keepass2android/QuickUnlock.cs
@@ -305,6 +305,15 @@ namespace keepass2android
}
}
+ protected override void OnPause()
+ {
+ base.OnPause();
+ if (_fingerprintDec != null)
+ {
+ _fingerprintDec.StopListening();
+ }
+ }
+
protected override void OnDestroy()
{
base.OnDestroy();
diff --git a/src/keepass2android/Resources/drawable-mdpi/ic_fp_40px.png b/src/keepass2android/Resources/drawable-mdpi/ic_fp_40px.png
new file mode 100644
index 00000000..122f4425
Binary files /dev/null and b/src/keepass2android/Resources/drawable-mdpi/ic_fp_40px.png differ
diff --git a/src/keepass2android/Resources/drawable-xhdpi/ic_fp_40px.png b/src/keepass2android/Resources/drawable-xhdpi/ic_fp_40px.png
new file mode 100644
index 00000000..e1c9590b
Binary files /dev/null and b/src/keepass2android/Resources/drawable-xhdpi/ic_fp_40px.png differ
diff --git a/src/keepass2android/Resources/drawable/ic_fingerprint_error.xml b/src/keepass2android/Resources/drawable/ic_fingerprint_error.xml
new file mode 100644
index 00000000..be46116d
--- /dev/null
+++ b/src/keepass2android/Resources/drawable/ic_fingerprint_error.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
diff --git a/src/keepass2android/Resources/drawable/ic_fingerprint_success.xml b/src/keepass2android/Resources/drawable/ic_fingerprint_success.xml
new file mode 100644
index 00000000..261f3e7f
--- /dev/null
+++ b/src/keepass2android/Resources/drawable/ic_fingerprint_success.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
diff --git a/src/keepass2android/Resources/layout/fingerprint_setup.xml b/src/keepass2android/Resources/layout/fingerprint_setup.xml
new file mode 100644
index 00000000..40b8441c
--- /dev/null
+++ b/src/keepass2android/Resources/layout/fingerprint_setup.xml
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file