/* This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin. Keepass2Android is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Keepass2Android is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Keepass2Android. If not, see . */ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using System.Xml; using System.Xml.Serialization; using Android; using Android.App; using Android.Content; using Android.Database; using Android.Graphics.Drawables; using Android.OS; using Android.Runtime; using Android.Views; using Android.Views.InputMethods; using Android.Widget; using Java.Net; using Android.Preferences; using Android.Text; using Android.Content.PM; using Android.Graphics; using Android.Hardware.Fingerprints; using Android.Provider; using Android.Support.Design.Widget; using Android.Support.V4.Widget; using Android.Support.V7.App; using Java.Lang; using keepass2android; using KeePassLib.Keys; using KeePassLib.Serialization; using KeePassLib.Utility; using Keepass2android.Pluginsdk; using OtpKeyProv; using keepass2android.Io; using keepass2android.Utils; using File = Java.IO.File; using FileNotFoundException = Java.IO.FileNotFoundException; using Object = Java.Lang.Object; using Process = Android.OS.Process; using KeeChallenge; using AlertDialog = Android.App.AlertDialog; using Enum = System.Enum; using Exception = System.Exception; using String = System.String; using Toolbar = Android.Support.V7.Widget.Toolbar; namespace keepass2android { [Activity(Label = "@string/app_name", ConfigurationChanges = ConfigChanges.Orientation, LaunchMode = LaunchMode.SingleInstance, WindowSoftInputMode = SoftInput.AdjustResize, Theme = "@style/MyTheme_Blue")] /*caution: also contained in AndroidManifest.xml*/ //CAUTION: don't add intent filters here, they must be set in the manifest xml file public class PasswordActivity : LockingActivity, IFingerprintAuthCallback { enum KeyProviders { //int values correspond to indices in passwordSpinner None = 0, KeyFile = 1, Otp = 2, OtpRecovery = 3, Challenge = 4, ChalRecovery = 5 } public const String KeyDefaultFilename = "defaultFileName"; public const String KeyFilename = "fileName"; private const String KeyKeyfile = "keyFile"; private const String KeyPassword = "password"; public const String KeyServerusername = "serverCredUser"; public const String KeyServerpassword = "serverCredPwd"; public const String KeyServercredmode = "serverCredRememberMode"; private const String ViewIntent = "android.intent.action.VIEW"; private const string ShowpasswordKey = "ShowPassword"; private const string KeyProviderIdOtp = "KP2A-OTP"; private const string KeyProviderIdOtpRecovery = "KP2A-OTPSecret"; private const string KeyProviderIdChallenge = "KP2A-Chal"; private const string KeyProviderIdChallengeRecovery = "KP2A-ChalSecret"; private const int RequestCodePrepareDbFile = 1000; private const int RequestCodePrepareOtpAuxFile = 1001; private const int RequestCodeChallengeYubikey = 1002; private const int RequestCodeSelectKeyfile = 1003; private const int RequestCodePrepareKeyFile = 1004; private const int RequestCodeSelectAuxFile = 1005; private Task _loadDbTask; private bool _loadDbTaskOffline; //indicate if preloading was started with offline mode private IOConnectionInfo _ioConnection; private String _keyFileOrProvider; bool _showPassword; internal AppTask AppTask; private bool _killOnDestroy; private string _password = ""; //OTPs which should be entered into the OTP fields as soon as these become visible private List _pendingOtps = new List(); KeyProviders KeyProviderType { get { if (_keyFileOrProvider == null) return KeyProviders.None; if (_keyFileOrProvider == KeyProviderIdOtp) return KeyProviders.Otp; if (_keyFileOrProvider == KeyProviderIdOtpRecovery) return KeyProviders.OtpRecovery; if (_keyFileOrProvider == KeyProviderIdChallenge) return KeyProviders.Challenge; if (_keyFileOrProvider == KeyProviderIdChallengeRecovery) return KeyProviders.ChalRecovery; return KeyProviders.KeyFile; } } private bool _rememberKeyfile; ISharedPreferences _prefs; private bool _starting; private OtpInfo _otpInfo; private IOConnectionInfo _otpAuxIoc; private ChallengeInfo _chalInfo; private byte[] _challengeSecret; private KeeChallengeProv _challengeProv; private readonly int[] _otpTextViewIds = new[] {Resource.Id.otp1, Resource.Id.otp2, Resource.Id.otp3, Resource.Id.otp4, Resource.Id.otp5, Resource.Id.otp6}; private const string OtpInfoKey = "OtpInfoKey"; private const string EnteredOtpsKey = "EnteredOtpsKey"; private const string PendingOtpsKey = "PendingOtpsKey"; private const string PasswordKey = "PasswordKey"; private const string KeyFileOrProviderKey = "KeyFileOrProviderKey"; private bool _performingLoad; private bool _keepPasswordInOnResume; private Typeface _passwordFont; private ActionBarDrawerToggle mDrawerToggle; private DrawerLayout _drawerLayout; public PasswordActivity (IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) { _activityDesign = new ActivityDesign(this); } public PasswordActivity() { _activityDesign = new ActivityDesign(this); } public static void PutIoConnectionToIntent(IOConnectionInfo ioc, Intent i) { i.PutExtra(KeyFilename, ioc.Path); i.PutExtra(KeyServerusername, ioc.UserName); i.PutExtra(KeyServerpassword, ioc.Password); i.PutExtra(KeyServercredmode, (int)ioc.CredSaveMode); } public static void SetIoConnectionFromIntent(IOConnectionInfo ioc, Intent i) { ioc.Path = i.GetStringExtra(KeyFilename); ioc.UserName = i.GetStringExtra(KeyServerusername) ?? ""; ioc.Password = i.GetStringExtra(KeyServerpassword) ?? ""; ioc.CredSaveMode = (IOCredSaveMode)i.GetIntExtra(KeyServercredmode, (int) IOCredSaveMode.NoSave); } public static void Launch(Activity act, String fileName, AppTask appTask) { File dbFile = new File(fileName); if ( ! dbFile.Exists() ) { throw new FileNotFoundException(); } Intent i = new Intent(act, typeof(PasswordActivity)); i.SetFlags(ActivityFlags.ForwardResult); i.PutExtra(KeyFilename, fileName); appTask.ToIntent(i); act.StartActivity(i); } public static void Launch(Activity act, IOConnectionInfo ioc, AppTask appTask) { if (ioc.IsLocalFile()) { Launch(act, ioc.Path, appTask); return; } Intent i = new Intent(act, typeof(PasswordActivity)); PutIoConnectionToIntent(ioc, i); i.SetFlags(ActivityFlags.ForwardResult); appTask.ToIntent(i); act.StartActivity(i); } public void LaunchNextActivity() { AppTask.AfterUnlockDatabase(this); } protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) { base.OnActivityResult(requestCode, resultCode, data); _keepPasswordInOnResume = true; Kp2aLog.Log("PasswordActivity.OnActivityResult "+resultCode+"/"+requestCode); AppTask.TryGetFromActivityResult(data, ref AppTask); //NOTE: original code from k eepassdroid used switch ((Android.App.Result)requestCode) { (but doesn't work here, although k eepassdroid works) switch(resultCode) { case KeePass.ExitNormal: // Returned to this screen using the Back key if (PreferenceManager.GetDefaultSharedPreferences(this) .GetBoolean(GetString(Resource.String.LockWhenNavigateBack_key), false)) { App.Kp2a.LockDatabase(); } //by leaving the app with the back button, the user probably wants to cancel the task //The activity might be resumed (through Android's recent tasks list), then use a NullTask: AppTask = new NullTask(); Finish(); break; case KeePass.ExitLock: // The database has already been locked, and the quick unlock screen will be shown if appropriate _rememberKeyfile = _prefs.GetBoolean(GetString(Resource.String.keyfile_key), Resources.GetBoolean(Resource.Boolean.keyfile_default)); //update value if ((KeyProviderType == KeyProviders.KeyFile) && (_rememberKeyfile)) { //check if the keyfile was changed (by importing to internal directory) var newKeyFile = GetKeyFile(_ioConnection.Path); if (newKeyFile != _keyFileOrProvider) { _keyFileOrProvider = newKeyFile; UpdateKeyfileIocView(); } } break; case KeePass.ExitCloseAfterTaskComplete: // Do not lock the database SetResult(KeePass.ExitCloseAfterTaskComplete); Finish(); break; case KeePass.ExitClose: SetResult(KeePass.ExitClose); Finish(); break; case KeePass.ExitReloadDb: if (App.Kp2a.GetDb().Loaded) { //remember the composite key for reloading: var compositeKey = App.Kp2a.GetDb().KpDatabase.MasterKey; //lock the database: App.Kp2a.LockDatabase(false); //reload the database (without most other stuff performed in PerformLoadDatabase. // We're assuming that the db file (and if appropriate also the key file) are still available // and there's no need to re-init the file storage. if it is, loading will fail and the user has // to retry with typing the full password, but that's intended to avoid showing the password to a // a potentially unauthorized user (feature request https://keepass2android.codeplex.com/workitem/274) Handler handler = new Handler(); OnFinish onFinish = new AfterLoad(handler, this); _performingLoad = true; LoadDb task = new LoadDb(App.Kp2a, _ioConnection, _loadDbTask, compositeKey, _keyFileOrProvider, onFinish); _loadDbTask = null; // prevent accidental re-use new ProgressTask(App.Kp2a, this, task).Run(); } break; case Result.Ok: if (requestCode == RequestCodeSelectKeyfile) { IOConnectionInfo ioc = new IOConnectionInfo(); SetIoConnectionFromIntent(ioc, data); _keyFileOrProvider = IOConnectionInfo.SerializeToString(ioc); UpdateKeyfileIocView(); } break; case (Result)FileStorageResults.FileUsagePrepared: if (requestCode == RequestCodePrepareDbFile) { if (KeyProviderType == KeyProviders.KeyFile) { //if the user has not yet selected a keyfile, _keyFileOrProvider is empty if (string.IsNullOrEmpty(_keyFileOrProvider) == false) { var iocKeyfile = IOConnectionInfo.UnserializeFromString(_keyFileOrProvider); App.Kp2a.GetFileStorage(iocKeyfile) .PrepareFileUsage(new FileStorageSetupInitiatorActivity(this, OnActivityResult, null), iocKeyfile, RequestCodePrepareKeyFile, false); } } else PerformLoadDatabase(); } if (requestCode == RequestCodePrepareKeyFile) { PerformLoadDatabase(); } if (requestCode == RequestCodePrepareOtpAuxFile) { GetAuxFileLoader().LoadAuxFile(true); } break; } if (requestCode == RequestCodeSelectAuxFile && resultCode == Result.Ok) { IOConnectionInfo auxFileIoc = new IOConnectionInfo(); SetIoConnectionFromIntent(auxFileIoc, data); PreferenceManager.GetDefaultSharedPreferences(this).Edit() .PutString("KP2A.PasswordAct.AuxFileIoc" + IOConnectionInfo.SerializeToString(_ioConnection), IOConnectionInfo.SerializeToString(auxFileIoc)) .Apply(); GetAuxFileLoader().LoadAuxFile(false); } if (requestCode == RequestCodeChallengeYubikey && resultCode == Result.Ok) { try { _challengeProv = new KeeChallengeProv(); byte[] challengeResponse = data.GetByteArrayExtra("response"); _challengeSecret = _challengeProv.GetSecret(_chalInfo, challengeResponse); Array.Clear(challengeResponse, 0, challengeResponse.Length); } catch (Exception e) { Kp2aLog.Log(e.ToString()); Toast.MakeText(this, "Error: " + e.Message, ToastLength.Long).Show(); return; } UpdateOkButtonState(); FindViewById(Resource.Id.otpInitView).Visibility = ViewStates.Gone; if (_challengeSecret != null) { new LoadingDialog(this, true, //doInBackground delegate { //save aux file try { ChallengeInfo temp = _challengeProv.Encrypt(_challengeSecret); if (!temp.Save(_otpAuxIoc)) { Toast.MakeText(this, Resource.String.ErrorUpdatingChalAuxFile, ToastLength.Long).Show(); return false; } } catch (Exception e) { Kp2aLog.LogUnexpectedError(e); } return null; } , delegate { }).Execute(); } else { Toast.MakeText(this, Resource.String.bad_resp, ToastLength.Long).Show(); return; } } } private AuxFileLoader GetAuxFileLoader() { if (_keyFileOrProvider == KeyProviderIdChallenge) { return new ChallengeAuxFileLoader(this); } else { return new OtpAuxFileLoader(this); } } private void UpdateKeyfileIocView() { //store keyfile in the view so that we can show the selected keyfile again if the user switches to another key provider and back to key file FindViewById(Resource.Id.label_keyfilename).Tag = _keyFileOrProvider; if (string.IsNullOrEmpty(_keyFileOrProvider)) { FindViewById(Resource.Id.filestorage_label).Visibility = ViewStates.Gone; FindViewById(Resource.Id.filestorage_logo).Visibility = ViewStates.Gone; FindViewById(Resource.Id.label_keyfilename).Text = Resources.GetString(Resource.String.no_keyfile_selected); return; } var ioc = IOConnectionInfo.UnserializeFromString(_keyFileOrProvider); string displayPath = App.Kp2a.GetFileStorage(ioc).GetDisplayName(ioc); int protocolSeparatorPos = displayPath.IndexOf("://", StringComparison.Ordinal); string protocolId = protocolSeparatorPos < 0 ? "file" : displayPath.Substring(0, protocolSeparatorPos); Drawable drawable = App.Kp2a.GetResourceDrawable("ic_storage_" + protocolId); FindViewById(Resource.Id.filestorage_logo).SetImageDrawable(drawable); FindViewById(Resource.Id.filestorage_logo).Visibility = ViewStates.Visible; String title = App.Kp2a.GetResourceString("filestoragename_" + protocolId); FindViewById(Resource.Id.filestorage_label).Text = title; FindViewById(Resource.Id.filestorage_label).Visibility = ViewStates.Visible; FindViewById(Resource.Id.label_keyfilename).Text = protocolSeparatorPos < 0 ? displayPath : displayPath.Substring(protocolSeparatorPos + 3); } private abstract class AuxFileLoader { protected readonly PasswordActivity Activity; protected AuxFileLoader(PasswordActivity activity) { Activity = activity; } public void LoadAuxFile(bool triggerSelectAuxManuallyOnFailure) { new LoadingDialog(Activity, true, //doInBackground delegate { try { var iocAux = GetDefaultAuxLocation(); LoadFile(iocAux); } catch (Exception e) { //this can happen e.g. if the file storage does not support GetParentPath Kp2aLog.Log(e.ToString()); //retry with saved ioc try { var savedManualIoc = IOConnectionInfo.UnserializeFromString( PreferenceManager.GetDefaultSharedPreferences(Activity).GetString( "KP2A.PasswordAct.AuxFileIoc" + IOConnectionInfo.SerializeToString(Activity._ioConnection), null)); LoadFile((savedManualIoc)); } catch (Exception e2) { Kp2aLog.LogUnexpectedError(e2); } } return null; } , delegate { if (!AuxDataLoaded) { if (triggerSelectAuxManuallyOnFailure) { Intent intent = new Intent(Activity, typeof(SelectStorageLocationActivity)); intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppGet, true); intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppSend, false); intent.PutExtra(FileStorageSetupDefs.ExtraIsForSave, false); intent.PutExtra(SelectStorageLocationActivity.ExtraKeyWritableRequirements, (int)SelectStorageLocationActivity.WritableRequirements.WriteDemanded); Activity.StartActivityForResult(intent, RequestCodeSelectAuxFile); } else { Toast.MakeText(Activity,GetErrorMessage(), ToastLength.Long).Show(); } return; } HandleSuccess(); }).Execute(); } protected abstract bool AuxDataLoaded { get; } protected abstract void LoadFile(IOConnectionInfo iocAux); protected abstract void HandleSuccess(); protected abstract string GetErrorMessage(); protected abstract IOConnectionInfo GetDefaultAuxLocation(); } private class OtpAuxFileLoader : AuxFileLoader { public OtpAuxFileLoader(PasswordActivity activity) : base(activity) { } protected override bool AuxDataLoaded { get { return Activity._otpInfo != null; } } protected override void LoadFile(IOConnectionInfo iocAux) { Activity._otpInfo = OtpInfo.Load(iocAux); Activity._otpAuxIoc = iocAux; } private static IOConnectionInfo GetAuxFileIoc(IOConnectionInfo databaseIoc) { IFileStorage fileStorage = App.Kp2a.GetOtpAuxFileStorage(databaseIoc); var parentPath = fileStorage.GetParentPath(databaseIoc); var filename = fileStorage.GetFilenameWithoutPathAndExt(databaseIoc) + OathHotpKeyProv.AuxFileExt; IOConnectionInfo iocAux = fileStorage.GetFilePath(parentPath, filename); return iocAux; } private static IOConnectionInfo GetAuxFileIoc(KeyProviderQueryContext ctx) { IOConnectionInfo ioc = ctx.DatabaseIOInfo.CloneDeep(); var iocAux = GetAuxFileIoc(ioc); return iocAux; } protected override void HandleSuccess() { IList prefilledOtps = Activity._pendingOtps; Activity.ShowOtpEntry(prefilledOtps); Activity._pendingOtps.Clear(); } protected override string GetErrorMessage() { return Activity.GetString(Resource.String.CouldntLoadOtpAuxFile) + " " + Activity.GetString(Resource.String.CouldntLoadOtpAuxFile_Hint); } protected override IOConnectionInfo GetDefaultAuxLocation() { return GetAuxFileIoc(Activity._ioConnection); } } private class ChallengeAuxFileLoader : AuxFileLoader { public ChallengeAuxFileLoader(PasswordActivity activity) : base(activity) { } protected override void HandleSuccess() { Intent chalIntent = new Intent("com.yubichallenge.NFCActivity.CHALLENGE"); chalIntent.PutExtra("challenge", Activity._chalInfo.Challenge); chalIntent.PutExtra("slot", 2); IList activities = Activity.PackageManager.QueryIntentActivities(chalIntent, 0); bool isIntentSafe = activities.Count > 0; if (isIntentSafe) { Activity.StartActivityForResult(chalIntent, RequestCodeChallengeYubikey); } else { AlertDialog.Builder b = new AlertDialog.Builder(Activity); b.SetMessage(Resource.String.YubiChallengeNotInstalled); b.SetPositiveButton(Android.Resource.String.Ok, delegate { Util.GotoUrl(Activity, Activity.GetString(Resource.String.MarketURL) + "com.yubichallenge"); }); b.SetNegativeButton(Resource.String.cancel, delegate { }); b.Create().Show(); } } protected override string GetErrorMessage() { return Activity.GetString(Resource.String.CouldntLoadChalAuxFile) + " " + Activity.GetString( Resource.String.CouldntLoadChalAuxFile_Hint); } protected override bool AuxDataLoaded { get { return Activity._chalInfo != null; } } protected override void LoadFile(IOConnectionInfo iocAux) { Activity._chalInfo = ChallengeInfo.Load(iocAux); Activity._otpAuxIoc = iocAux; } protected override IOConnectionInfo GetDefaultAuxLocation() { IFileStorage fileStorage = App.Kp2a.GetOtpAuxFileStorage(Activity._ioConnection); IOConnectionInfo iocAux = fileStorage.GetFilePath( fileStorage.GetParentPath(Activity._ioConnection), fileStorage.GetFilenameWithoutPathAndExt(Activity._ioConnection) + ".xml"); return iocAux; } } private void ShowOtpEntry(IList prefilledOtps) { FindViewById(Resource.Id.otpInitView).Visibility = ViewStates.Gone; FindViewById(Resource.Id.otpEntry).Visibility = ViewStates.Visible; int c = 0; foreach (int otpId in _otpTextViewIds) { c++; var otpTextView = FindViewById(otpId); if (c <= prefilledOtps.Count) { otpTextView.Text = prefilledOtps[c - 1]; } else { otpTextView.Text = ""; } otpTextView.Hint = GetString(Resource.String.otp_hint, new Object[] {c}); otpTextView.SetFilters(new IInputFilter[] {new InputFilterLengthFilter((int) _otpInfo.OtpLength)}); if (c > _otpInfo.OtpsRequired) { otpTextView.Visibility = ViewStates.Gone; } else { otpTextView.TextChanged += (sender, args) => { UpdateOkButtonState(); }; } } } int count = 1; private DrawerLayout mDrawerLayout; //private RecyclerView mDrawerList; private string mDrawerTitle; private MeasuringRelativeLayout.MeasureArgs _measureArgs; private ActivityDesign _activityDesign; private FingerprintDecryption _fingerprintDec; private bool _fingerprintPermissionGranted; private PasswordActivityBroadcastReceiver _intentReceiver; private int _appnameclickCount; internal class MyActionBarDrawerToggle : ActionBarDrawerToggle { PasswordActivity owner; public MyActionBarDrawerToggle(PasswordActivity activity, DrawerLayout layout, int imgRes, int openRes, int closeRes) : base(activity, layout, openRes, closeRes) { owner = activity; } public override void OnDrawerClosed(View drawerView) { owner.SupportActionBar.Title = owner.Title; owner.InvalidateOptionsMenu(); } public override void OnDrawerOpened(View drawerView) { owner.SupportActionBar.Title = owner.mDrawerTitle; owner.InvalidateOptionsMenu(); } } private void UncollapseToolbar() { AppBarLayout appbarLayout = FindViewById(Resource.Id.appbar); var tmp = appbarLayout.LayoutParameters; CoordinatorLayout.LayoutParams p = tmp.JavaCast(); var tmp2 = p.Behavior; var behavior = tmp2.JavaCast(); if (behavior == null) { p.Behavior = behavior = new AppBarLayout.Behavior(); } behavior.OnNestedFling(FindViewById(Resource.Id.main_content), appbarLayout, null, 0, -10000, false); } private void CollapseToolbar() { AppBarLayout appbarLayout = FindViewById(Resource.Id.appbar); ViewGroup.LayoutParams tmp = appbarLayout.LayoutParameters; CoordinatorLayout.LayoutParams p = tmp.JavaCast(); var tmp2 = p.Behavior; var behavior = tmp2.JavaCast(); if (behavior == null) { p.Behavior = behavior = new AppBarLayout.Behavior(); } behavior.OnNestedFling(FindViewById(Resource.Id.main_content), appbarLayout, null, 0, 200, true); } protected override void OnCreate(Bundle savedInstanceState) { _activityDesign.ApplyTheme(); base.OnCreate(savedInstanceState); _intentReceiver = new PasswordActivityBroadcastReceiver(this); IntentFilter filter = new IntentFilter(); filter.AddAction(Intent.ActionScreenOff); RegisterReceiver(_intentReceiver, filter); //use FlagSecure to make sure the last (revealed) character of the master password is not visible in recent apps if (PreferenceManager.GetDefaultSharedPreferences(this).GetBoolean( GetString(Resource.String.ViewDatabaseSecure_key), true)) { Window.SetFlags(WindowManagerFlags.Secure, WindowManagerFlags.Secure); } Intent i = Intent; //only load the AppTask if this is the "first" OnCreate (not because of kill/resume, i.e. savedInstanceState==null) // and if the activity is not launched from history (i.e. recent tasks) because this would mean that // the Activity was closed already (user cancelling the task or task complete) but is restarted due recent tasks. // Don't re-start the task (especially bad if tak was complete already) if (Intent.Flags.HasFlag(ActivityFlags.LaunchedFromHistory)) { AppTask = new NullTask(); } else { AppTask = AppTask.GetTaskInOnCreate(savedInstanceState, Intent); } String action = i.Action; _prefs = PreferenceManager.GetDefaultSharedPreferences(this); _rememberKeyfile = _prefs.GetBoolean(GetString(Resource.String.keyfile_key), Resources.GetBoolean(Resource.Boolean.keyfile_default)); _ioConnection = new IOConnectionInfo(); if (action != null && action.Equals(ViewIntent)) { if (!GetIocFromViewIntent(i)) return; } else if ((action != null) && (action.Equals(Intents.StartWithOtp))) { if (!GetIocFromOtpIntent(savedInstanceState, i)) return; _keepPasswordInOnResume = true; } else { SetIoConnectionFromIntent(_ioConnection, i); var keyFileFromIntent = i.GetStringExtra(KeyKeyfile); if (keyFileFromIntent != null) { Kp2aLog.Log("try get keyfile from intent"); _keyFileOrProvider = IOConnectionInfo.SerializeToString(IOConnectionInfo.FromPath(keyFileFromIntent)); Kp2aLog.Log("try get keyfile from intent ok"); } else { _keyFileOrProvider = null; } _password = i.GetStringExtra(KeyPassword) ?? ""; if (string.IsNullOrEmpty(_keyFileOrProvider)) { _keyFileOrProvider = GetKeyFile(_ioConnection.Path); } if ((!string.IsNullOrEmpty(_keyFileOrProvider)) || (_password != "")) { _keepPasswordInOnResume = true; } } if (App.Kp2a.GetDb().Loaded && App.Kp2a.GetDb().Ioc != null && App.Kp2a.GetDb().Ioc.GetDisplayName() != _ioConnection.GetDisplayName()) { // A different database is currently loaded, unload it before loading the new one requested App.Kp2a.LockDatabase(false); } SetContentView(Resource.Layout.password); InitializeToolbar(); InitializeFilenameView(); if (KeyProviderType == KeyProviders.KeyFile) { UpdateKeyfileIocView(); } var passwordEdit = FindViewById(Resource.Id.password_edit); passwordEdit.TextChanged += (sender, args) => { _password = passwordEdit.Text; UpdateOkButtonState(); }; passwordEdit.EditorAction += (sender, args) => { if ((args.ActionId == ImeAction.Done) || ((args.ActionId == ImeAction.ImeNull) && (args.Event.Action == KeyEventActions.Down))) OnOk(); }; FindViewById(Resource.Id.pass_otpsecret).TextChanged += (sender, args) => UpdateOkButtonState(); passwordEdit.Text = _password; var passwordFont = Typeface.CreateFromAsset(Assets, "SourceCodePro-Regular.ttf"); passwordEdit.Typeface = passwordFont; InitializeBottomBarButtons(); InitializePasswordModeSpinner(); InitializeOtpSecretSpinner(); InitializeNavDrawerButtons(); UpdateOkButtonState(); InitializeTogglePasswordButton(); InitializeKeyfileBrowseButton(); InitializeOptionCheckboxes(); RestoreState(savedInstanceState); if (i.GetBooleanExtra("launchImmediately", false)) { App.Kp2a.GetFileStorage(_ioConnection) .PrepareFileUsage(new FileStorageSetupInitiatorActivity(this, OnActivityResult, null), _ioConnection, RequestCodePrepareDbFile, false); } mDrawerTitle = this.Title; mDrawerLayout = FindViewById(Resource.Id.drawer_layout); var rootview = FindViewById(Resource.Id.relative_layout); rootview.ViewTreeObserver.GlobalLayout += (sender, args2) => { Android.Util.Log.Debug("KP2A", "GlobalLayout"); var args = _measureArgs; if (args == null) return; Android.Util.Log.Debug("KP2A", "ActualHeight=" + args.ActualHeight); Android.Util.Log.Debug("KP2A", "ProposedHeight=" + args.ProposedHeight); if (args.ActualHeight < args.ProposedHeight) UncollapseToolbar(); if (args.ActualHeight > args.ProposedHeight) CollapseToolbar(); }; rootview.MeasureEvent += (sender, args) => { //Snackbar.Make(rootview, "height="+args.ActualHeight, Snackbar.LengthLong).Show(); this._measureArgs = args; }; if ((int)Build.VERSION.SdkInt >= 23) RequestPermissions(new[] { Manifest.Permission.UseFingerprint }, FingerprintPermissionRequestCode); } const int FingerprintPermissionRequestCode = 0; public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults) { if ((requestCode == FingerprintPermissionRequestCode) && (grantResults.Length > 0) && (grantResults[0] == Permission.Granted)) { var btn = FindViewById(Resource.Id.fingerprintbtn); btn.Click += (sender, args) => { AlertDialog.Builder b = new AlertDialog.Builder(this); b.SetTitle(Resource.String.fingerprint_prefs); b.SetMessage(btn.Tag.ToString()); b.SetPositiveButton(Android.Resource.String.Ok, (o, eventArgs) => ((Dialog)o).Dismiss()); b.Show(); }; _fingerprintPermissionGranted = true; } } private void ClearFingerprintUnlockData() { ISharedPreferencesEditor edit = PreferenceManager.GetDefaultSharedPreferences(this).Edit(); edit.PutString(Database.GetFingerprintPrefKey(_ioConnection), ""); edit.PutString(Database.GetFingerprintModePrefKey(_ioConnection), FingerprintUnlockMode.Disabled.ToString()); edit.Commit(); } public void OnFingerprintError(string message) { var btn = FindViewById(Resource.Id.fingerprintbtn); btn.SetImageResource(Resource.Drawable.ic_fingerprint_error); btn.PostDelayed(() => { btn.SetImageResource(Resource.Drawable.ic_fp_40px); btn.Tag = GetString(Resource.String.fingerprint_unlock_hint); }, 1300); Toast.MakeText(this, message, ToastLength.Long).Show(); } public void OnFingerprintAuthSucceeded() { var btn = FindViewById(Resource.Id.fingerprintbtn); btn.SetImageResource(Resource.Drawable.ic_fingerprint_success); var masterPassword = _fingerprintDec.DecryptStored(Database.GetFingerprintPrefKey(_ioConnection)); _password = FindViewById(Resource.Id.password_edit).Text = masterPassword; btn.PostDelayed(() => { //re-init fingerprint unlock in case something goes wrong with opening the database InitFingerprintUnlock(); //fire OnOk(true); }, 1000); } private void InitializeNavDrawerButtons() { FindViewById(Resource.Id.btn_nav_change_db).Click += (sender, args) => { GoToFileSelectActivity(); }; FindViewById(Resource.Id.btn_nav_donate).Click += (sender, args) => { Util.GotoDonateUrl(this); }; FindViewById(Resource.Id.btn_nav_donate).Visibility = PreferenceManager.GetDefaultSharedPreferences(this) .GetBoolean(this.GetString(Resource.String.NoDonateOption_key), false) ? ViewStates.Gone : ViewStates.Visible; FindViewById(Resource.Id.btn_nav_about).Click += (sender, args) => { AboutDialog dialog = new AboutDialog(this); dialog.Show(); }; FindViewById(Resource.Id.btn_nav_settings).Click += (sender, args) => { AppSettingsActivity.Launch(this); }; FindViewById(Resource.Id.nav_app_name).Click += (sender, args) => { _appnameclickCount++; if (_appnameclickCount == 6) { Kp2aLog.LogUnexpectedError(new Exception("some blabla")); Toast.MakeText(this, "Once again and the app will crash.", ToastLength.Long).Show(); } if (_appnameclickCount == 7) { throw new Exception("this is an easter egg crash (to test uncaught exceptions."); } }; } private void InitializeToolbar() { var toolbar = FindViewById(Resource.Id.mytoolbar); SetSupportActionBar(toolbar); var collapsingToolbar = FindViewById(Resource.Id.collapsing_toolbar); collapsingToolbar.SetTitle(GetString(Resource.String.unlock_database_title)); _drawerLayout = FindViewById(Resource.Id.drawer_layout); mDrawerToggle = new ActionBarDrawerToggle(this, _drawerLayout, Resource.String.menu_open, Resource.String.menu_close); _drawerLayout.SetDrawerListener(mDrawerToggle); SupportActionBar.SetDisplayHomeAsUpEnabled(true); SupportActionBar.SetHomeButtonEnabled(true); mDrawerToggle.SyncState(); } public override void OnBackPressed() { if (_drawerLayout.IsDrawerOpen((int) GravityFlags.Start)) { _drawerLayout.CloseDrawer((int)GravityFlags.Start); return; } base.OnBackPressed(); } private void InitializeOtpSecretSpinner() { Spinner spinner = FindViewById(Resource.Id.otpsecret_format_spinner); ArrayAdapter spinnerArrayAdapter = new ArrayAdapter(this, Android.Resource.Layout.SimpleSpinnerDropDownItem, EncodingUtil.Formats); spinner.Adapter = spinnerArrayAdapter; } private bool GetIocFromOtpIntent(Bundle savedInstanceState, Intent i) { //create called after detecting an OTP via NFC //this means the Activity was not on the back stack before, i.e. no database has been selected _ioConnection = null; //see if we can get a database from recent: if (App.Kp2a.FileDbHelper.HasRecentFiles()) { ICursor filesCursor = App.Kp2a.FileDbHelper.FetchAllFiles(); StartManagingCursor(filesCursor); filesCursor.MoveToFirst(); IOConnectionInfo ioc = App.Kp2a.FileDbHelper.CursorToIoc(filesCursor); if (App.Kp2a.GetFileStorage(ioc).RequiresSetup(ioc) == false) { IFileStorage fileStorage = App.Kp2a.GetFileStorage(ioc); if (!fileStorage.RequiresCredentials(ioc)) { //ok, we can use this file _ioConnection = ioc; } } } if (_ioConnection == null) { //We need to go to FileSelectActivity first. //For security reasons: discard the OTP (otherwise the user might not select a database now and forget //about the OTP, but it would still be stored in the Intents and later be passed to PasswordActivity again. Toast.MakeText(this, GetString(Resource.String.otp_discarded_because_no_db), ToastLength.Long).Show(); GoToFileSelectActivity(); return false; } //assume user wants to use OTP (for static password, they need to open KP2A first and select the key provider type, then see OnNewIntent) _keyFileOrProvider = KeyProviderIdOtp; if (savedInstanceState == null) //only when not re-creating { //remember the OTP for later use _pendingOtps.Add(i.GetStringExtra(Intents.OtpExtraKey)); i.RemoveExtra(Intents.OtpExtraKey); } return true; } private bool GetIocFromViewIntent(Intent i) { //started from "view" intent (e.g. from file browser) _ioConnection.Path = i.DataString; if (_ioConnection.Path.StartsWith("file://")) { _ioConnection.Path = URLDecoder.Decode(_ioConnection.Path.Substring(7)); if (_ioConnection.Path.Length == 0) { // No file name Toast.MakeText(this, Resource.String.FileNotFound, ToastLength.Long).Show(); Finish(); return false; } File dbFile = new File(_ioConnection.Path); if (!dbFile.Exists()) { // File does not exist Toast.MakeText(this, Resource.String.FileNotFound, ToastLength.Long).Show(); Finish(); return false; } } else { if (!_ioConnection.Path.StartsWith("content://")) { Toast.MakeText(this, Resource.String.error_can_not_handle_uri, ToastLength.Long).Show(); Finish(); return false; } } _keyFileOrProvider = GetKeyFile(_ioConnection.Path); return true; } private void InitializeBottomBarButtons() { Button confirmButton = (Button) FindViewById(Resource.Id.pass_ok); confirmButton.Click += (sender, e) => { OnOk(); }; var changeDbButton = FindViewById