add StopListening in OnPause of QuickUnlock

add missing files
This commit is contained in:
Philipp Crocoll 2016-01-05 12:18:22 +01:00
parent 161964bff7
commit b1dcc4f7a9
9 changed files with 936 additions and 0 deletions

View 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));
}
}
}

View 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));
}
}
}

View File

@ -0,0 +1,9 @@
namespace keepass2android
{
public enum FingerprintUnlockMode
{
Disabled = 0,
QuickUnlock = 1,
FullUnlock = 2,
}
}

View File

@ -305,6 +305,15 @@ namespace keepass2android
}
}
protected override void OnPause()
{
base.OnPause();
if (_fingerprintDec != null)
{
_fingerprintDec.StopListening();
}
}
protected override void OnDestroy()
{
base.OnDestroy();

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -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>

View File

@ -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>

View 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>