344 lines
11 KiB
C#
344 lines
11 KiB
C#
using System;
|
|
using System.Linq;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
|
|
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 Google.Android.Material.Dialog;
|
|
using Java.Lang;
|
|
using keepass2android;
|
|
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.Keyboard | ConfigChanges.KeyboardHidden,
|
|
Theme = "@style/Kp2aTheme_ActionBar", MainLauncher = false, Exported = true)]
|
|
[IntentFilter(new[] { "kp2a.action.FingerprintSetupActivity" }, Categories = new[] { Intent.CategoryDefault })]
|
|
public class BiometricSetupActivity : LockCloseActivity, IBiometricAuthCallback
|
|
{
|
|
private readonly ActivityDesign _activityDesign;
|
|
|
|
public BiometricSetupActivity(IntPtr javaReference, JniHandleOwnership transfer)
|
|
: base(javaReference, transfer)
|
|
{
|
|
_activityDesign = new ActivityDesign(this);
|
|
}
|
|
public BiometricSetupActivity()
|
|
{
|
|
_activityDesign = new ActivityDesign(this);
|
|
}
|
|
|
|
|
|
|
|
private FingerprintUnlockMode _unlockMode = FingerprintUnlockMode.Disabled;
|
|
private FingerprintUnlockMode _desiredUnlockMode;
|
|
private BiometricEncryption _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.CurrentDb.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;
|
|
|
|
FindViewById<CheckBox>(Resource.Id.close_database_after_failed).Checked =
|
|
Util.GetCloseDatabaseAfterFailedBiometricQuickUnlock(this);
|
|
|
|
FindViewById<CheckBox>(Resource.Id.close_database_after_failed).CheckedChange += (sender, args) =>
|
|
{
|
|
PreferenceManager.GetDefaultSharedPreferences(this)
|
|
.Edit()
|
|
.PutBoolean(GetString(Resource.String.CloseDatabaseAfterFailedBiometricQuickUnlock_key), args.IsChecked)
|
|
.Commit();
|
|
};
|
|
|
|
|
|
UpdateCloseDatabaseAfterFailedBiometricQuickUnlockVisibility();
|
|
|
|
|
|
}
|
|
|
|
private void UpdateCloseDatabaseAfterFailedBiometricQuickUnlockVisibility()
|
|
{
|
|
FindViewById(Resource.Id.close_database_after_failed).Visibility = _unlockMode == FingerprintUnlockMode.QuickUnlock ? ViewStates.Visible : ViewStates.Gone;
|
|
}
|
|
|
|
string CurrentPreferenceKey
|
|
{
|
|
get { return App.Kp2a.CurrentDb.CurrentFingerprintPrefKey; }
|
|
}
|
|
|
|
private void StoreUnlockMode()
|
|
{
|
|
ISharedPreferencesEditor edit = PreferenceManager.GetDefaultSharedPreferences(this).Edit();
|
|
if (_unlockMode == FingerprintUnlockMode.Disabled)
|
|
{
|
|
edit.PutString(CurrentPreferenceKey, "");
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
if (_unlockMode == FingerprintUnlockMode.FullUnlock)
|
|
{
|
|
var userKey = App.Kp2a.CurrentDb.KpDatabase.MasterKey.GetUserKey<KcpPassword>();
|
|
_enc.StoreEncrypted(userKey != null ? userKey.Password.ReadString() : "", CurrentPreferenceKey, edit);
|
|
}
|
|
else
|
|
_enc.StoreEncrypted("QuickUnlock" /*some dummy data*/, CurrentPreferenceKey, edit);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
new MaterialAlertDialogBuilder(this)
|
|
.SetTitle(GetString(Resource.String.ErrorOcurred))
|
|
.SetMessage(GetString(Resource.String.FingerprintSetupFailed))
|
|
.SetCancelable(false)
|
|
.SetPositiveButton(Android.Resource.String.Ok, (sender, args) => { })
|
|
.Show();
|
|
|
|
}
|
|
|
|
}
|
|
edit.PutString(App.Kp2a.CurrentDb.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;
|
|
}
|
|
|
|
|
|
private void ShowRadioButtons()
|
|
{
|
|
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 HideRadioButtons()
|
|
{
|
|
FindViewById<TextView>(Resource.Id.tvFatalError).Visibility = ViewStates.Gone;
|
|
FindViewById(Resource.Id.radio_buttons).Visibility = ViewStates.Gone;
|
|
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;
|
|
UpdateCloseDatabaseAfterFailedBiometricQuickUnlockVisibility();
|
|
|
|
StoreUnlockMode();
|
|
return;
|
|
}
|
|
|
|
_desiredUnlockMode = newMode;
|
|
FindViewById(Resource.Id.radio_buttons).Visibility = ViewStates.Gone;
|
|
UpdateCloseDatabaseAfterFailedBiometricQuickUnlockVisibility();
|
|
|
|
FindViewById(Resource.Id.fingerprint_auth_container).Visibility = ViewStates.Visible;
|
|
try
|
|
{
|
|
_enc = new BiometricEncryption(new BiometricModule(this), CurrentPreferenceKey);
|
|
if (!_enc.Init())
|
|
throw new Exception("Failed to initialize cipher");
|
|
ResetErrorTextRunnable();
|
|
|
|
_enc.StartListening(new BiometricAuthCallbackAdapter(this, this));
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
CheckCurrentRadioButton();
|
|
App.Kp2a.ShowMessage(this, e.ToString(), MessageSeverity.Error);
|
|
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 OnBiometricAuthSucceeded()
|
|
{
|
|
_unlockMode = _desiredUnlockMode;
|
|
|
|
_fpTextView.RemoveCallbacks(ResetErrorTextRunnable);
|
|
_fpIcon.SetImageResource(Resource.Drawable.ic_fingerprint_success);
|
|
_fpTextView.SetTextColor(_fpTextView.Resources.GetColor(Resource.Color.md_theme_secondary, 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();
|
|
UpdateCloseDatabaseAfterFailedBiometricQuickUnlockVisibility();
|
|
|
|
|
|
}, SUCCESS_DELAY_MILLIS);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public void OnBiometricError(string error)
|
|
{
|
|
_fpIcon.SetImageResource(Resource.Drawable.ic_fingerprint_error);
|
|
_fpTextView.Text = error;
|
|
_fpTextView.SetTextColor(
|
|
_fpTextView.Resources.GetColor(Resource.Color.md_theme_error, null));
|
|
_fpTextView.RemoveCallbacks(ResetErrorTextRunnable);
|
|
_fpTextView.PostDelayed(ResetErrorTextRunnable, ERROR_TIMEOUT_MILLIS);
|
|
}
|
|
|
|
public void OnBiometricAttemptFailed(string message)
|
|
{
|
|
//ignore
|
|
}
|
|
|
|
void ResetErrorTextRunnable()
|
|
{
|
|
_fpTextView.SetTextColor(
|
|
_fpTextView.Resources.GetColor(Resource.Color.md_theme_secondary, null));
|
|
_fpTextView.Text = "";
|
|
_fpIcon.SetImageResource(Resource.Drawable.baseline_fingerprint_24);
|
|
}
|
|
|
|
protected override void OnResume()
|
|
{
|
|
base.OnResume();
|
|
|
|
BiometricModule fpModule = new BiometricModule(this);
|
|
HideRadioButtons();
|
|
if (!fpModule.IsHardwareAvailable)
|
|
{
|
|
SetError(Resource.String.fingerprint_hardware_error);
|
|
UpdateCloseDatabaseAfterFailedBiometricQuickUnlockVisibility();
|
|
return;
|
|
}
|
|
if (!fpModule.IsAvailable)
|
|
{
|
|
SetError(Resource.String.fingerprint_no_enrolled);
|
|
return;
|
|
}
|
|
ShowRadioButtons();
|
|
UpdateCloseDatabaseAfterFailedBiometricQuickUnlockVisibility();
|
|
|
|
}
|
|
|
|
protected override void OnPause()
|
|
{
|
|
base.OnPause();
|
|
if (_enc != null)
|
|
_enc.StopListening();
|
|
}
|
|
}
|
|
|
|
} |