mirror of
https://github.com/moparisthebest/keepass2android
synced 2024-11-26 03:02:24 -05:00
add StopListening in OnPause of QuickUnlock
add missing files
This commit is contained in:
parent
161964bff7
commit
b1dcc4f7a9
434
src/keepass2android/FingerprintModule.cs
Normal file
434
src/keepass2android/FingerprintModule.cs
Normal file
@ -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();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a symmetric key in the Android Key Store which can only be used after the user
|
||||
/// has authenticated with fingerprint.
|
||||
/// </summary>
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
313
src/keepass2android/FingerprintSetupActivity.cs
Normal file
313
src/keepass2android/FingerprintSetupActivity.cs
Normal file
@ -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<ImageView>(Resource.Id.fingerprint_icon);
|
||||
_fpTextView = FindViewById<TextView>(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<RadioButton>).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<KcpPassword>().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<TextView>(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<TextView>(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));
|
||||
}
|
||||
}
|
||||
}
|
9
src/keepass2android/FingerprintUnlockMode.cs
Normal file
9
src/keepass2android/FingerprintUnlockMode.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace keepass2android
|
||||
{
|
||||
public enum FingerprintUnlockMode
|
||||
{
|
||||
Disabled = 0,
|
||||
QuickUnlock = 1,
|
||||
FullUnlock = 2,
|
||||
}
|
||||
}
|
@ -305,6 +305,15 @@ namespace keepass2android
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPause()
|
||||
{
|
||||
base.OnPause();
|
||||
if (_fingerprintDec != null)
|
||||
{
|
||||
_fingerprintDec.StopListening();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
BIN
src/keepass2android/Resources/drawable-mdpi/ic_fp_40px.png
Normal file
BIN
src/keepass2android/Resources/drawable-mdpi/ic_fp_40px.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
BIN
src/keepass2android/Resources/drawable-xhdpi/ic_fp_40px.png
Normal file
BIN
src/keepass2android/Resources/drawable-xhdpi/ic_fp_40px.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2015 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40.0dp"
|
||||
android:height="40.0dp"
|
||||
android:viewportWidth="40.0"
|
||||
android:viewportHeight="40.0">
|
||||
<path
|
||||
android:pathData="M20.0,0.0C8.96,0.0 0.0,8.95 0.0,20.0s8.96,20.0 20.0,20.0c11.04,0.0 20.0,-8.95 20.0,-20.0S31.04,0.0 20.0,0.0z"
|
||||
android:fillColor="#F4511E"/>
|
||||
<path
|
||||
android:pathData="M21.33,29.33l-2.67,0.0l0.0,-2.67l2.67,0.0L21.33,29.33zM21.33,22.67l-2.67,0.0l0.0,-12.0l2.67,0.0L21.33,22.67z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
</vector>
|
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2015 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40.0dp"
|
||||
android:height="40.0dp"
|
||||
android:viewportWidth="40.0"
|
||||
android:viewportHeight="40.0">
|
||||
<path
|
||||
android:pathData="M20.0,20.0m-20.0,0.0a20.0,20.0 0.0,1.0 1.0,40.0 0.0a20.0,20.0 0.0,1.0 1.0,-40.0 0.0"
|
||||
android:fillColor="#009688"/>
|
||||
<path
|
||||
android:pathData="M11.2,21.41l1.63,-1.619999 4.17,4.169998 10.59,-10.589999 1.619999,1.63 -12.209999,12.209999z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
</vector>
|
115
src/keepass2android/Resources/layout/fingerprint_setup.xml
Normal file
115
src/keepass2android/Resources/layout/fingerprint_setup.xml
Normal file
@ -0,0 +1,115 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="fill_parent"
|
||||
android:background="?activityBackgroundColor"
|
||||
android:layout_height="fill_parent"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginBottom="12dip"
|
||||
android:layout_marginLeft="12dip"
|
||||
android:layout_marginRight="12dip"
|
||||
android:layout_marginTop="12dip">
|
||||
<TextView
|
||||
android:id="@+id/tvFatalError"
|
||||
android:text="@string/fingerprint_os_error"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
<LinearLayout
|
||||
android:id="@+id/radio_buttons"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<RadioButton
|
||||
android:id="@+id/radio_fingerprint_disabled"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/disable_fingerprint_unlock" />
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<RadioButton
|
||||
android:id="@+id/radio_fingerprint_quickunlock"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/enable_fingerprint_quickunlock" />
|
||||
<keepass2android.views.Kp2aShortHelpView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/TextAppearance_Help_Dense"
|
||||
android:textColor="@color/light_gray"
|
||||
app:help_text="@string/enable_fingerprint_quickunlock_Info"
|
||||
app:title_text="@string/enable_fingerprint_quickunlock"
|
||||
android:background="?android:attr/selectableItemBackground" />
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<RadioButton
|
||||
android:id="@+id/radio_fingerprint_unlock"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/enable_fingerprint_unlock" />
|
||||
<keepass2android.views.Kp2aShortHelpView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/TextAppearance_Help_Dense"
|
||||
android:textColor="@color/light_gray"
|
||||
app:help_text="@string/enable_fingerprint_unlock_Info"
|
||||
app:title_text="@string/enable_fingerprint_unlock"
|
||||
android:background="?android:attr/selectableItemBackground" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
<RelativeLayout
|
||||
android:id="@+id/fingerprint_auth_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="8dp"
|
||||
android:paddingStart="24dp"
|
||||
android:paddingEnd="24dp"
|
||||
android:paddingTop="16dp">
|
||||
<TextView
|
||||
android:id="@+id/fingerprint_description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:text="@string/fingerprint_description"
|
||||
android:textAppearance="@android:style/TextAppearance.Material.Subhead"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
<ImageView
|
||||
android:id="@+id/fingerprint_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_below="@+id/fingerprint_description"
|
||||
android:layout_marginTop="20dp"
|
||||
android:src="@drawable/ic_fp_40px" />
|
||||
<TextView
|
||||
android:id="@+id/fingerprint_status"
|
||||
style="@style/TextAppearance.AppCompat.Body1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBottom="@+id/fingerprint_icon"
|
||||
android:layout_alignTop="@+id/fingerprint_icon"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_toEndOf="@+id/fingerprint_icon"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/fingerprint_hint"
|
||||
android:textColor="@color/hint_color" />
|
||||
<Button
|
||||
android:id="@+id/cancel_button"
|
||||
android:text="@string/cancel"
|
||||
android:paddingTop="16dp"
|
||||
android:layout_below="@id/fingerprint_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
Loading…
Reference in New Issue
Block a user