extended implementation of OTP
This commit is contained in:
		@@ -60,7 +60,7 @@ namespace keepass2android.Io
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class CachingFileStorage: IFileStorage
 | 
			
		||||
	{
 | 
			
		||||
		private readonly IFileStorage _cachedStorage;
 | 
			
		||||
		protected readonly IFileStorage _cachedStorage;
 | 
			
		||||
		private readonly ICacheSupervisor _cacheSupervisor;
 | 
			
		||||
		private readonly string _streamCacheDir;
 | 
			
		||||
 | 
			
		||||
@@ -179,14 +179,21 @@ namespace keepass2android.Io
 | 
			
		||||
				{
 | 
			
		||||
					if (TryUpdateRemoteFile(localData, ioc, true, hash))
 | 
			
		||||
						_cacheSupervisor.UpdatedRemoteFileOnLoad(ioc);
 | 
			
		||||
					return File.OpenRead(cachedFilePath);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				//conflict: both files changed.
 | 
			
		||||
				//signal that we're loading from local
 | 
			
		||||
				_cacheSupervisor.NotifyOpenFromLocalDueToConflict(ioc);
 | 
			
		||||
				return OpenFileForReadWithConflict(ioc, cachedFilePath);
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		protected virtual Stream OpenFileForReadWithConflict(IOConnectionInfo ioc, string cachedFilePath)
 | 
			
		||||
		{
 | 
			
		||||
			//signal that we're loading from local
 | 
			
		||||
			_cacheSupervisor.NotifyOpenFromLocalDueToConflict(ioc);
 | 
			
		||||
			return File.OpenRead(cachedFilePath);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -214,29 +221,17 @@ namespace keepass2android.Io
 | 
			
		||||
 | 
			
		||||
		private Stream OpenFileForReadWhenNoLocalChanges(IOConnectionInfo ioc, string cachedFilePath)
 | 
			
		||||
		{
 | 
			
		||||
			//open stream:
 | 
			
		||||
			using (Stream file = _cachedStorage.OpenFileForRead(ioc))
 | 
			
		||||
			{
 | 
			
		||||
 | 
			
		||||
			//remember current hash
 | 
			
		||||
			string previousHash = null;
 | 
			
		||||
			string baseVersionFilePath = BaseVersionFilePath(ioc);
 | 
			
		||||
			if (File.Exists(baseVersionFilePath))
 | 
			
		||||
				previousHash = File.ReadAllText(baseVersionFilePath);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
				//copy to cache: 
 | 
			
		||||
				//note: we might use the file version to check if it's already in the cache and if copying is required. 
 | 
			
		||||
				//However, this is safer.
 | 
			
		||||
				string fileHash;
 | 
			
		||||
				using (HashingStreamEx cachedFile = new HashingStreamEx(File.Create(cachedFilePath), true, new SHA256Managed()))
 | 
			
		||||
				{
 | 
			
		||||
					file.CopyTo(cachedFile);
 | 
			
		||||
					cachedFile.Close();
 | 
			
		||||
					fileHash = MemUtil.ByteArrayToHexString(cachedFile.Hash);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				//remember current hash
 | 
			
		||||
				string previousHash = null;
 | 
			
		||||
				string baseVersionFilePath = BaseVersionFilePath(ioc);
 | 
			
		||||
				if (File.Exists(baseVersionFilePath))
 | 
			
		||||
					previousHash = File.ReadAllText(baseVersionFilePath);
 | 
			
		||||
 | 
			
		||||
				//save hash in cache files:
 | 
			
		||||
				File.WriteAllText(VersionFilePath(ioc), fileHash);
 | 
			
		||||
				File.WriteAllText(baseVersionFilePath, fileHash);
 | 
			
		||||
				var fileHash = UpdateCacheFromRemote(ioc, cachedFilePath);
 | 
			
		||||
 | 
			
		||||
				//notify supervisor what we did:
 | 
			
		||||
				if (previousHash != fileHash)
 | 
			
		||||
@@ -245,8 +240,35 @@ namespace keepass2android.Io
 | 
			
		||||
					_cacheSupervisor.LoadedFromRemoteInSync(ioc);
 | 
			
		||||
 | 
			
		||||
				return File.OpenRead(cachedFilePath);	
 | 
			
		||||
 | 
			
		||||
			
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// copies the file in ioc to the local cache. Updates the cache version files and returns the new file hash.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		protected string UpdateCacheFromRemote(IOConnectionInfo ioc, string cachedFilePath)
 | 
			
		||||
		{
 | 
			
		||||
			//note: we might use the file version to check if it's already in the cache and if copying is required. 
 | 
			
		||||
			//However, this is safer.
 | 
			
		||||
			string fileHash;
 | 
			
		||||
			
 | 
			
		||||
			//open stream:
 | 
			
		||||
			using (Stream remoteFile = _cachedStorage.OpenFileForRead(ioc))
 | 
			
		||||
			{
 | 
			
		||||
 | 
			
		||||
				using (HashingStreamEx cachedFile = new HashingStreamEx(File.Create(cachedFilePath), true, new SHA256Managed()))
 | 
			
		||||
				{
 | 
			
		||||
					remoteFile.CopyTo(cachedFile);
 | 
			
		||||
					cachedFile.Close();
 | 
			
		||||
					fileHash = MemUtil.ByteArrayToHexString(cachedFile.Hash);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			//save hash in cache files:
 | 
			
		||||
			File.WriteAllText(VersionFilePath(ioc), fileHash);
 | 
			
		||||
			File.WriteAllText(BaseVersionFilePath(ioc), fileHash);
 | 
			
		||||
			return fileHash;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private bool TryUpdateRemoteFile(Stream cachedData, IOConnectionInfo ioc, bool useFileTransaction, string hash)
 | 
			
		||||
@@ -266,7 +288,7 @@ namespace keepass2android.Io
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private void UpdateRemoteFile(Stream cachedData, IOConnectionInfo ioc, bool useFileTransaction, string hash)
 | 
			
		||||
		protected void UpdateRemoteFile(Stream cachedData, IOConnectionInfo ioc, bool useFileTransaction, string hash)
 | 
			
		||||
		{
 | 
			
		||||
			//try to write to remote:
 | 
			
		||||
			using (
 | 
			
		||||
 
 | 
			
		||||
@@ -44,6 +44,8 @@ namespace keepass2android
 | 
			
		||||
		CheckingDatabaseForChanges,
 | 
			
		||||
		RemoteDatabaseUnchanged,
 | 
			
		||||
		CannotMoveGroupHere,
 | 
			
		||||
		ErrorOcurred
 | 
			
		||||
		ErrorOcurred,
 | 
			
		||||
		SynchronizingOtpAuxFile,
 | 
			
		||||
		SavingOtpAuxFile
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -44,6 +44,13 @@ namespace keepass2android
 | 
			
		||||
				return KpDatabase == null ? null : KpDatabase.IOConnectionInfo;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// if an OTP key was used, this property tells the location of the OTP auxiliary file.
 | 
			
		||||
		/// Must be set after loading.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public IOConnectionInfo OtpAuxFileIoc { get; set; }
 | 
			
		||||
 | 
			
		||||
		public string LastFileVersion;
 | 
			
		||||
		public SearchDbHelper SearchHelper;
 | 
			
		||||
		
 | 
			
		||||
@@ -192,6 +199,7 @@ namespace keepass2android
 | 
			
		||||
			KpDatabase = null;
 | 
			
		||||
			_loaded = false;
 | 
			
		||||
			_reloadRequested = false;
 | 
			
		||||
			OtpAuxFileIoc = null;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		public void MarkAllGroupsAsDirty() {
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,7 @@ using Android.Widget;
 | 
			
		||||
using KeePassLib;
 | 
			
		||||
using Android.Preferences;
 | 
			
		||||
using KeePassLib.Interfaces;
 | 
			
		||||
using KeePassLib.Serialization;
 | 
			
		||||
using KeePassLib.Utility;
 | 
			
		||||
using keepass2android.Io;
 | 
			
		||||
using keepass2android.database.edit;
 | 
			
		||||
@@ -367,6 +368,37 @@ namespace keepass2android
 | 
			
		||||
			return base.OnOptionsItemSelected(item);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		class SyncOtpAuxFile: OnFinish
 | 
			
		||||
		{
 | 
			
		||||
			private readonly IOConnectionInfo _ioc;
 | 
			
		||||
 | 
			
		||||
			public SyncOtpAuxFile(IOConnectionInfo ioc)
 | 
			
		||||
			{
 | 
			
		||||
				_ioc = ioc;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			public override void Run()
 | 
			
		||||
			{
 | 
			
		||||
				if (Handler != null)
 | 
			
		||||
				{
 | 
			
		||||
					Handler.Post(DoSyncOtpAuxFile);
 | 
			
		||||
				}
 | 
			
		||||
				else
 | 
			
		||||
					DoSyncOtpAuxFile();
 | 
			
		||||
				base.Run();
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			private void DoSyncOtpAuxFile()
 | 
			
		||||
			{
 | 
			
		||||
				StatusLogger.UpdateMessage(UiStringKey.SynchronizingOtpAuxFile);
 | 
			
		||||
				//simply open the file. The file storage does a complete sync.
 | 
			
		||||
				using (App.Kp2a.GetOtpAuxFileStorage(_ioc).OpenFileForRead(_ioc))
 | 
			
		||||
				{
 | 
			
		||||
					
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private void Synchronize()
 | 
			
		||||
		{
 | 
			
		||||
			var filestorage = App.Kp2a.GetFileStorage(App.Kp2a.GetDb().Ioc);
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ using Android.App;
 | 
			
		||||
using Android.Content;
 | 
			
		||||
using Android.Content.PM;
 | 
			
		||||
using Android.OS;
 | 
			
		||||
using Android.Widget;
 | 
			
		||||
using Java.Util.Regex;
 | 
			
		||||
 | 
			
		||||
namespace keepass2android
 | 
			
		||||
@@ -54,8 +55,30 @@ namespace keepass2android
 | 
			
		||||
			
 | 
			
		||||
			
 | 
			
		||||
			i.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
 | 
			
		||||
			i.PutExtra(Intents.OtpExtraKey, GetOtpFromIntent(Intent));
 | 
			
		||||
			StartActivity(i);
 | 
			
		||||
			try
 | 
			
		||||
			{
 | 
			
		||||
				string otp = GetOtpFromIntent(Intent);
 | 
			
		||||
				if (otp == null)
 | 
			
		||||
					throw new Exception("Otp must not be null!");
 | 
			
		||||
				i.PutExtra(Intents.OtpExtraKey, otp);
 | 
			
		||||
			}
 | 
			
		||||
			catch (Exception e)
 | 
			
		||||
			{
 | 
			
		||||
				Kp2aLog.Log(e.ToString());
 | 
			
		||||
				Toast.MakeText(this, "No Yubikey OTP found!", ToastLength.Long).Show();
 | 
			
		||||
				Finish();
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (App.Kp2a.GetDb().Loaded)
 | 
			
		||||
			{
 | 
			
		||||
				Toast.MakeText(this, GetString(Resource.String.otp_discarded_because_db_open), ToastLength.Long).Show();
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				StartActivity(i);				
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			Finish();
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,10 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using System.Xml;
 | 
			
		||||
using System.Xml.Serialization;
 | 
			
		||||
using Android.App;
 | 
			
		||||
using Android.Content;
 | 
			
		||||
using Android.Database;
 | 
			
		||||
@@ -27,15 +30,17 @@ using Android.Views;
 | 
			
		||||
using Android.Widget;
 | 
			
		||||
using Java.Net;
 | 
			
		||||
using Android.Preferences;
 | 
			
		||||
using Java.IO;
 | 
			
		||||
using Android.Text;
 | 
			
		||||
using Android.Content.PM;
 | 
			
		||||
using KeePassLib.Keys;
 | 
			
		||||
using KeePassLib.Serialization;
 | 
			
		||||
using KeePassLib.Utility;
 | 
			
		||||
using OtpKeyProv;
 | 
			
		||||
using keepass2android.Io;
 | 
			
		||||
using keepass2android.Utils;
 | 
			
		||||
using Exception = System.Exception;
 | 
			
		||||
using File = Java.IO.File;
 | 
			
		||||
using FileNotFoundException = Java.IO.FileNotFoundException;
 | 
			
		||||
using MemoryStream = System.IO.MemoryStream;
 | 
			
		||||
using Object = Java.Lang.Object;
 | 
			
		||||
using Process = Android.OS.Process;
 | 
			
		||||
@@ -55,11 +60,10 @@ namespace keepass2android
 | 
			
		||||
			//int values correspond to indices in passwordSpinner
 | 
			
		||||
			None = 0,
 | 
			
		||||
			KeyFile = 1,
 | 
			
		||||
			Otp = 2
 | 
			
		||||
			Otp = 2,
 | 
			
		||||
			OtpRecovery = 3
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		bool _showPassword;
 | 
			
		||||
 | 
			
		||||
		public const String KeyDefaultFilename = "defaultFileName";
 | 
			
		||||
 | 
			
		||||
		public const String KeyFilename = "fileName";
 | 
			
		||||
@@ -71,19 +75,23 @@ namespace keepass2android
 | 
			
		||||
		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 int RequestCodePrepareDbFile = 1000;
 | 
			
		||||
		private const int RequestCodePrepareOtpAuxFile = 1001;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		private Task<MemoryStream> _loadDbTask;
 | 
			
		||||
		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 readonly List<String> _pendingOtps = new List<string>();
 | 
			
		||||
		private List<String> _pendingOtps = new List<string>();
 | 
			
		||||
 | 
			
		||||
		private const int RequestCodePrepareDbFile = 1000;
 | 
			
		||||
		private const int RequestCodePrepareOtpAuxFile = 1001;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		KeyProviders KeyProviderType
 | 
			
		||||
@@ -94,6 +102,8 @@ namespace keepass2android
 | 
			
		||||
					return KeyProviders.None;
 | 
			
		||||
				if (_keyFileOrProvider == KeyProviderIdOtp)
 | 
			
		||||
					return KeyProviders.Otp;
 | 
			
		||||
				if (_keyFileOrProvider == KeyProviderIdOtpRecovery)
 | 
			
		||||
					return KeyProviders.OtpRecovery;
 | 
			
		||||
				return KeyProviders.KeyFile;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
@@ -103,7 +113,12 @@ namespace keepass2android
 | 
			
		||||
 | 
			
		||||
		private bool _starting;
 | 
			
		||||
		private OtpInfo _otpInfo;
 | 
			
		||||
		private readonly int[] _otpTextViewIds = new int[] {Resource.Id.otp1, Resource.Id.otp2, Resource.Id.otp3, Resource.Id.otp4, Resource.Id.otp5, Resource.Id.otp6};
 | 
			
		||||
		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";
 | 
			
		||||
 | 
			
		||||
		public PasswordActivity (IntPtr javaReference, JniHandleOwnership transfer)
 | 
			
		||||
			: base(javaReference, transfer)
 | 
			
		||||
@@ -217,7 +232,7 @@ namespace keepass2android
 | 
			
		||||
							KcpKeyFile kcpKeyfile = (KcpKeyFile)App.Kp2a.GetDb().KpDatabase.MasterKey.GetUserKey(typeof(KcpKeyFile));
 | 
			
		||||
 | 
			
		||||
							SetEditText(Resource.Id.pass_keyfile, kcpKeyfile.Path);
 | 
			
		||||
							_keyFileOrProvider = kcpKeyfile.Path;
 | 
			
		||||
							
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					App.Kp2a.LockDatabase(false);
 | 
			
		||||
@@ -234,7 +249,7 @@ namespace keepass2android
 | 
			
		||||
							
 | 
			
		||||
							EditText fn = (EditText) FindViewById(Resource.Id.pass_keyfile);
 | 
			
		||||
							fn.Text = filename;
 | 
			
		||||
							_keyFileOrProvider = filename;
 | 
			
		||||
							
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					break;
 | 
			
		||||
@@ -265,51 +280,54 @@ namespace keepass2android
 | 
			
		||||
							Toast.MakeText(this, GetString(Resource.String.CouldntLoadOtpAuxFile), ToastLength.Long).Show();
 | 
			
		||||
							return;
 | 
			
		||||
						}
 | 
			
		||||
						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<EditText>(otpId);
 | 
			
		||||
							if (c <= _pendingOtps.Count)
 | 
			
		||||
							{
 | 
			
		||||
								otpTextView.Text = _pendingOtps[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();
 | 
			
		||||
								};
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
						IList<string> prefilledOtps = _pendingOtps;
 | 
			
		||||
						ShowOtpEntry(prefilledOtps);
 | 
			
		||||
						_pendingOtps.Clear();
 | 
			
		||||
						
 | 
			
		||||
					}
 | 
			
		||||
			).Execute();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private void ShowOtpEntry(IList<string> 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<EditText>(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(); };
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		protected override void OnCreate(Bundle savedInstanceState)
 | 
			
		||||
		{
 | 
			
		||||
			base.OnCreate(savedInstanceState);
 | 
			
		||||
			if (savedInstanceState != null)
 | 
			
		||||
				_showPassword = savedInstanceState.GetBoolean(ShowpasswordKey, false);
 | 
			
		||||
 | 
			
		||||
			Intent i = Intent;
 | 
			
		||||
 | 
			
		||||
			AppTask = AppTask.GetTaskInOnCreate(savedInstanceState, Intent);
 | 
			
		||||
 | 
			
		||||
			Intent i = Intent;
 | 
			
		||||
			String action = i.Action;
 | 
			
		||||
			
 | 
			
		||||
			_prefs = PreferenceManager.GetDefaultSharedPreferences(this);
 | 
			
		||||
@@ -320,84 +338,11 @@ namespace keepass2android
 | 
			
		||||
 | 
			
		||||
			if (action != null && action.Equals(ViewIntent))
 | 
			
		||||
			{
 | 
			
		||||
				//started from "view" intent (e.g. from file browser)
 | 
			
		||||
				_ioConnection.Path = i.DataString;
 | 
			
		||||
				
 | 
			
		||||
				if (! _ioConnection.Path.Substring(0, 7).Equals("file://"))
 | 
			
		||||
				{
 | 
			
		||||
					//TODO: this might no longer be required as we can handle http(s) and ftp as well (but we need server credentials therefore)
 | 
			
		||||
					Toast.MakeText(this, Resource.String.error_can_not_handle_uri, ToastLength.Long).Show();
 | 
			
		||||
					Finish();
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				_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;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				File dbFile = new File(_ioConnection.Path);
 | 
			
		||||
				if (! dbFile.Exists())
 | 
			
		||||
				{
 | 
			
		||||
					// File does not exist
 | 
			
		||||
					Toast.MakeText(this, Resource.String.FileNotFound, ToastLength.Long).Show();
 | 
			
		||||
					Finish();
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
				
 | 
			
		||||
				_keyFileOrProvider = GetKeyFile(_ioConnection.Path);
 | 
			
		||||
				
 | 
			
		||||
				if (!GetIocFromViewIntent(i)) return;
 | 
			
		||||
			} 
 | 
			
		||||
			else if ((action != null) && (action.Equals(Intents.StartWithOtp)))
 | 
			
		||||
			{
 | 
			
		||||
				//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();
 | 
			
		||||
					Finish();
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				//user obviously wants to use OTP:
 | 
			
		||||
				_keyFileOrProvider = KeyProviderIdOtp;
 | 
			
		||||
 | 
			
		||||
				//remember the OTP for later use
 | 
			
		||||
				_pendingOtps.Add(Intent.GetStringExtra(Intents.OtpExtraKey));
 | 
			
		||||
				Intent.RemoveExtra(Intents.OtpExtraKey);
 | 
			
		||||
				if (!GetIocFromOtpIntent(savedInstanceState, i)) return;
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
@@ -419,16 +364,17 @@ namespace keepass2android
 | 
			
		||||
			
 | 
			
		||||
			
 | 
			
		||||
			SetContentView(Resource.Layout.password);
 | 
			
		||||
			PopulateView();
 | 
			
		||||
			InitializeFilenameView();
 | 
			
		||||
 | 
			
		||||
			EditText passwordEdit = FindViewById<EditText>(Resource.Id.password);
 | 
			
		||||
			if (KeyProviderType == KeyProviders.KeyFile)
 | 
			
		||||
				SetEditText(Resource.Id.pass_keyfile, _keyFileOrProvider);
 | 
			
		||||
 | 
			
		||||
			FindViewById<EditText>(Resource.Id.pass_keyfile).TextChanged +=
 | 
			
		||||
				(sender, args) =>
 | 
			
		||||
					{
 | 
			
		||||
						_keyFileOrProvider = FindViewById<EditText>(Resource.Id.pass_keyfile).Text;
 | 
			
		||||
						UpdateOkButtonState();
 | 
			
		||||
					};
 | 
			
		||||
				{
 | 
			
		||||
					_keyFileOrProvider = FindViewById<EditText>(Resource.Id.pass_keyfile).Text;
 | 
			
		||||
					UpdateOkButtonState();
 | 
			
		||||
				};
 | 
			
		||||
 | 
			
		||||
			FindViewById<EditText>(Resource.Id.password).TextChanged +=
 | 
			
		||||
				(sender, args) =>
 | 
			
		||||
@@ -437,20 +383,167 @@ namespace keepass2android
 | 
			
		||||
					UpdateOkButtonState();
 | 
			
		||||
				};
 | 
			
		||||
 | 
			
		||||
			FindViewById<EditText>(Resource.Id.pass_otpsecret).TextChanged += (sender, args) => UpdateOkButtonState();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
			EditText passwordEdit = FindViewById<EditText>(Resource.Id.password);
 | 
			
		||||
			passwordEdit.RequestFocus();
 | 
			
		||||
			Window.SetSoftInputMode(SoftInput.StateVisible);
 | 
			
		||||
 | 
			
		||||
			Button confirmButton = (Button)FindViewById(Resource.Id.pass_ok);
 | 
			
		||||
			InitializeOkButton();
 | 
			
		||||
 | 
			
		||||
			InitializePasswordModeSpinner();
 | 
			
		||||
 | 
			
		||||
			InitializeOtpSecretSpinner();
 | 
			
		||||
 | 
			
		||||
			UpdateOkButtonState();
 | 
			
		||||
			
 | 
			
		||||
			InitializeTogglePasswordButton();
 | 
			
		||||
			InitializeKeyfileBrowseButton();
 | 
			
		||||
 | 
			
		||||
			InitializeQuickUnlockCheckbox();
 | 
			
		||||
 | 
			
		||||
			RestoreState(savedInstanceState);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private void InitializeOtpSecretSpinner()
 | 
			
		||||
		{
 | 
			
		||||
			Spinner spinner = FindViewById<Spinner>(Resource.Id.otpsecret_format_spinner);
 | 
			
		||||
			ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<String>(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();
 | 
			
		||||
				Finish();
 | 
			
		||||
				return false;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			//user obviously wants to use OTP:
 | 
			
		||||
			_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.Substring(0, 7).Equals("file://"))
 | 
			
		||||
			{
 | 
			
		||||
				//TODO: this might no longer be required as we can handle http(s) and ftp as well (but we need server credentials therefore)
 | 
			
		||||
				Toast.MakeText(this, Resource.String.error_can_not_handle_uri, ToastLength.Long).Show();
 | 
			
		||||
				Finish();
 | 
			
		||||
				return false;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			_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;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			_keyFileOrProvider = GetKeyFile(_ioConnection.Path);
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private void InitializeOkButton()
 | 
			
		||||
		{
 | 
			
		||||
			Button confirmButton = (Button) FindViewById(Resource.Id.pass_ok);
 | 
			
		||||
			confirmButton.Click += (sender, e) =>
 | 
			
		||||
				{
 | 
			
		||||
					App.Kp2a.GetFileStorage(_ioConnection)
 | 
			
		||||
					   .PrepareFileUsage(new FileStorageSetupInitiatorActivity(this, OnActivityResult, null), _ioConnection, RequestCodePrepareDbFile, false);
 | 
			
		||||
					   .PrepareFileUsage(new FileStorageSetupInitiatorActivity(this, OnActivityResult, null), _ioConnection,
 | 
			
		||||
					                     RequestCodePrepareDbFile, false);
 | 
			
		||||
				};
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private void InitializeTogglePasswordButton()
 | 
			
		||||
		{
 | 
			
		||||
			ImageButton btnTogglePassword = (ImageButton) FindViewById(Resource.Id.toggle_password);
 | 
			
		||||
			btnTogglePassword.Click += (sender, e) =>
 | 
			
		||||
				{
 | 
			
		||||
					_showPassword = !_showPassword;
 | 
			
		||||
					MakePasswordMaskedOrVisible();
 | 
			
		||||
				};
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private void InitializeKeyfileBrowseButton()
 | 
			
		||||
		{
 | 
			
		||||
			ImageButton browse = (ImageButton) FindViewById(Resource.Id.browse_button);
 | 
			
		||||
			browse.Click += (sender, evt) =>
 | 
			
		||||
				{
 | 
			
		||||
					string filename = null;
 | 
			
		||||
					if (!String.IsNullOrEmpty(_ioConnection.Path))
 | 
			
		||||
					{
 | 
			
		||||
						File keyfile = new File(_ioConnection.Path);
 | 
			
		||||
						File parent = keyfile.ParentFile;
 | 
			
		||||
						if (parent != null)
 | 
			
		||||
						{
 | 
			
		||||
							filename = parent.AbsolutePath;
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					Util.ShowBrowseDialog(filename, this, Intents.RequestCodeFileBrowseForKeyfile, false);
 | 
			
		||||
				};
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private void InitializePasswordModeSpinner()
 | 
			
		||||
		{
 | 
			
		||||
			Spinner passwordModeSpinner = FindViewById<Spinner>(Resource.Id.password_mode_spinner);
 | 
			
		||||
			if (passwordModeSpinner != null)
 | 
			
		||||
			{
 | 
			
		||||
 | 
			
		||||
				UpdateKeyProviderUiState();
 | 
			
		||||
				passwordModeSpinner.SetSelection((int) KeyProviderType);
 | 
			
		||||
				passwordModeSpinner.ItemSelected += (sender, args) =>
 | 
			
		||||
@@ -466,16 +559,20 @@ namespace keepass2android
 | 
			
		||||
							case 2:
 | 
			
		||||
								_keyFileOrProvider = KeyProviderIdOtp;
 | 
			
		||||
								break;
 | 
			
		||||
							case 3:
 | 
			
		||||
								_keyFileOrProvider = KeyProviderIdOtpRecovery;
 | 
			
		||||
								break;
 | 
			
		||||
							default:
 | 
			
		||||
								throw new Exception("Unexpected position "+args.Position+" / " + ((ICursor)((AdapterView)sender).GetItemAtPosition(args.Position)).GetString(1));
 | 
			
		||||
 | 
			
		||||
								throw new Exception("Unexpected position " + args.Position + " / " +
 | 
			
		||||
								                    ((ICursor) ((AdapterView) sender).GetItemAtPosition(args.Position)).GetString(1));
 | 
			
		||||
						}
 | 
			
		||||
						UpdateKeyProviderUiState();
 | 
			
		||||
					};
 | 
			
		||||
				FindViewById(Resource.Id.init_otp).Click += (sender, args) =>
 | 
			
		||||
					{
 | 
			
		||||
						App.Kp2a.GetOtpAuxFileStorage(_ioConnection)
 | 
			
		||||
						.PrepareFileUsage(new FileStorageSetupInitiatorActivity(this, OnActivityResult, null), _ioConnection, RequestCodePrepareOtpAuxFile, false);
 | 
			
		||||
						   .PrepareFileUsage(new FileStorageSetupInitiatorActivity(this, OnActivityResult, null), _ioConnection,
 | 
			
		||||
						                     RequestCodePrepareOtpAuxFile, false);
 | 
			
		||||
					};
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
@@ -483,51 +580,35 @@ namespace keepass2android
 | 
			
		||||
				//android 2.x 
 | 
			
		||||
				//TODO test
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
			UpdateOkButtonState();
 | 
			
		||||
			
 | 
			
		||||
			
 | 
			
		||||
			
 | 
			
		||||
			/*CheckBox checkBox = (CheckBox) FindViewById(Resource.Id.show_password);
 | 
			
		||||
			// Show or hide password
 | 
			
		||||
			checkBox.CheckedChange += delegate(object sender, CompoundButton.CheckedChangeEventArgs e) {
 | 
			
		||||
 | 
			
		||||
				TextView password = (TextView) FindViewById(Resource.Id.password);
 | 
			
		||||
				if ( e.IsChecked ) {
 | 
			
		||||
					password.InputType = InputTypes.ClassText | InputTypes.TextVariationVisiblePassword;
 | 
			
		||||
				} else {
 | 
			
		||||
					password.InputType = InputTypes.ClassText | InputTypes.TextVariationPassword;
 | 
			
		||||
				}
 | 
			
		||||
			};
 | 
			
		||||
			*/
 | 
			
		||||
			ImageButton btnTogglePassword = (ImageButton)FindViewById(Resource.Id.toggle_password);
 | 
			
		||||
			btnTogglePassword.Click += (sender, e) =>
 | 
			
		||||
				{
 | 
			
		||||
					_showPassword = !_showPassword;
 | 
			
		||||
					MakePasswordMaskedOrVisible();
 | 
			
		||||
				};
 | 
			
		||||
			
 | 
			
		||||
			
 | 
			
		||||
			
 | 
			
		||||
			ImageButton browse = (ImageButton)FindViewById(Resource.Id.browse_button);
 | 
			
		||||
			browse.Click += (sender, evt) => 
 | 
			
		||||
		private void RestoreState(Bundle savedInstanceState)
 | 
			
		||||
		{
 | 
			
		||||
			if (savedInstanceState != null)
 | 
			
		||||
			{
 | 
			
		||||
				string filename = null;
 | 
			
		||||
				if (!String.IsNullOrEmpty(_ioConnection.Path))
 | 
			
		||||
				_showPassword = savedInstanceState.GetBoolean(ShowpasswordKey, false);
 | 
			
		||||
				MakePasswordMaskedOrVisible();
 | 
			
		||||
 | 
			
		||||
				_keyFileOrProvider = FindViewById<EditText>(Resource.Id.pass_keyfile).Text = savedInstanceState.GetString(KeyFileOrProviderKey);
 | 
			
		||||
				_password = FindViewById<EditText>(Resource.Id.password).Text = savedInstanceState.GetString(PasswordKey);
 | 
			
		||||
 | 
			
		||||
				_pendingOtps = new List<string>(savedInstanceState.GetStringArrayList(PendingOtpsKey));
 | 
			
		||||
				
 | 
			
		||||
				string otpInfoString = savedInstanceState.GetString(OtpInfoKey);
 | 
			
		||||
				if (otpInfoString != null)
 | 
			
		||||
				{
 | 
			
		||||
					File keyfile = new File(_ioConnection.Path);
 | 
			
		||||
					File parent = keyfile.ParentFile;
 | 
			
		||||
					if (parent != null)
 | 
			
		||||
					{
 | 
			
		||||
						filename = parent.AbsolutePath;
 | 
			
		||||
					}
 | 
			
		||||
					
 | 
			
		||||
					XmlSerializer xs = new XmlSerializer(typeof(OtpInfo));
 | 
			
		||||
					_otpInfo = (OtpInfo)xs.Deserialize(new StringReader(otpInfoString));
 | 
			
		||||
 | 
			
		||||
					var enteredOtps = savedInstanceState.GetStringArrayList(EnteredOtpsKey);
 | 
			
		||||
 | 
			
		||||
					ShowOtpEntry(enteredOtps);
 | 
			
		||||
				}
 | 
			
		||||
				Util.ShowBrowseDialog(filename, this, Intents.RequestCodeFileBrowseForKeyfile, false);
 | 
			
		||||
				
 | 
			
		||||
			};
 | 
			
		||||
				UpdateKeyProviderUiState();
 | 
			
		||||
				
 | 
			
		||||
			RetrieveSettings();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private void UpdateOkButtonState()
 | 
			
		||||
@@ -562,6 +643,9 @@ namespace keepass2android
 | 
			
		||||
					
 | 
			
		||||
					FindViewById(Resource.Id.pass_ok).Enabled = enabled;
 | 
			
		||||
					break;
 | 
			
		||||
				case KeyProviders.OtpRecovery:
 | 
			
		||||
					FindViewById(Resource.Id.pass_ok).Enabled = FindViewById<EditText>(Resource.Id.pass_otpsecret).Text != "" && _password != "";
 | 
			
		||||
					break;
 | 
			
		||||
				default:
 | 
			
		||||
					throw new ArgumentOutOfRangeException();
 | 
			
		||||
			}
 | 
			
		||||
@@ -575,6 +659,10 @@ namespace keepass2android
 | 
			
		||||
			FindViewById(Resource.Id.otpView).Visibility = KeyProviderType == KeyProviders.Otp
 | 
			
		||||
				                                               ? ViewStates.Visible
 | 
			
		||||
				                                               : ViewStates.Gone;
 | 
			
		||||
 | 
			
		||||
			FindViewById(Resource.Id.otpSecretLine).Visibility = KeyProviderType == KeyProviders.OtpRecovery
 | 
			
		||||
															   ? ViewStates.Visible
 | 
			
		||||
															   : ViewStates.Gone;
 | 
			
		||||
			if (KeyProviderType == KeyProviders.Otp)
 | 
			
		||||
			{
 | 
			
		||||
				FindViewById(Resource.Id.otps_pending).Visibility = _pendingOtps.Count > 0 ? ViewStates.Visible : ViewStates.Gone;
 | 
			
		||||
@@ -597,7 +685,8 @@ namespace keepass2android
 | 
			
		||||
				catch (Exception e)
 | 
			
		||||
				{
 | 
			
		||||
					Kp2aLog.Log(e.ToString());
 | 
			
		||||
					throw new KeyFileException();
 | 
			
		||||
					Toast.MakeText(this, App.Kp2a.GetResourceString(UiStringKey.keyfile_does_not_exist), ToastLength.Long).Show();
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			else if (KeyProviderType == KeyProviders.Otp)
 | 
			
		||||
@@ -605,26 +694,34 @@ namespace keepass2android
 | 
			
		||||
 | 
			
		||||
				try
 | 
			
		||||
				{
 | 
			
		||||
					List<string> lOtps = new List<string>();
 | 
			
		||||
					foreach (int otpId in _otpTextViewIds)
 | 
			
		||||
					{
 | 
			
		||||
						string otpText = FindViewById<EditText>(otpId).Text;
 | 
			
		||||
						if (!String.IsNullOrEmpty(otpText))
 | 
			
		||||
							lOtps.Add(otpText);
 | 
			
		||||
					}
 | 
			
		||||
					var lOtps = GetOtpsFromUI();
 | 
			
		||||
					CreateOtpSecret(lOtps);
 | 
			
		||||
				}
 | 
			
		||||
				catch (Exception)
 | 
			
		||||
				{
 | 
			
		||||
					const string strMain = "Failed to create OTP key!";
 | 
			
		||||
					const string strLine1 = "Make sure you've entered the correct OTPs.";
 | 
			
		||||
 | 
			
		||||
					Toast.MakeText(this, strMain + " " + strLine1, ToastLength.Long).Show();
 | 
			
		||||
					Toast.MakeText(this, GetString(Resource.String.OtpKeyError), ToastLength.Long).Show();
 | 
			
		||||
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
				compositeKey.AddUserKey(new KcpCustomKey(OathHotpKeyProv.Name, _otpInfo.Secret, true));
 | 
			
		||||
			}
 | 
			
		||||
			else if (KeyProviderType == KeyProviders.OtpRecovery)
 | 
			
		||||
			{
 | 
			
		||||
				Spinner stpDataFmtSpinner = FindViewById<Spinner>(Resource.Id.otpsecret_format_spinner);
 | 
			
		||||
				EditText secretEdit = FindViewById<EditText>(Resource.Id.pass_otpsecret);
 | 
			
		||||
 | 
			
		||||
				byte[] pbSecret = EncodingUtil.ParseKey(secretEdit.Text, (OtpDataFmt)stpDataFmtSpinner.SelectedItemPosition);
 | 
			
		||||
				if (pbSecret != null)
 | 
			
		||||
				{
 | 
			
		||||
					compositeKey.AddUserKey(new KcpCustomKey(OathHotpKeyProv.Name, pbSecret, true));
 | 
			
		||||
				}
 | 
			
		||||
				else
 | 
			
		||||
				{
 | 
			
		||||
					Toast.MakeText(this, Resource.String.CouldntParseOtpSecret, ToastLength.Long).Show();
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			CheckBox cbQuickUnlock = (CheckBox) FindViewById(Resource.Id.enable_quickunlock);
 | 
			
		||||
			App.Kp2a.SetQuickUnlockEnabled(cbQuickUnlock.Checked);
 | 
			
		||||
@@ -642,6 +739,18 @@ namespace keepass2android
 | 
			
		||||
			new ProgressTask(App.Kp2a, this, task).Run();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private List<string> GetOtpsFromUI()
 | 
			
		||||
		{
 | 
			
		||||
			List<string> lOtps = new List<string>();
 | 
			
		||||
			foreach (int otpId in _otpTextViewIds)
 | 
			
		||||
			{
 | 
			
		||||
				string otpText = FindViewById<EditText>(otpId).Text;
 | 
			
		||||
				if (!String.IsNullOrEmpty(otpText))
 | 
			
		||||
					lOtps.Add(otpText);
 | 
			
		||||
			}
 | 
			
		||||
			return lOtps;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private void CreateOtpSecret(List<string> lOtps)
 | 
			
		||||
		{
 | 
			
		||||
			byte[] pbSecret;
 | 
			
		||||
@@ -785,13 +894,32 @@ namespace keepass2android
 | 
			
		||||
			base.OnSaveInstanceState(outState);
 | 
			
		||||
			AppTask.ToBundle(outState);
 | 
			
		||||
			outState.PutBoolean(ShowpasswordKey, _showPassword);
 | 
			
		||||
			//TODO: 
 | 
			
		||||
			// * save OTP state
 | 
			
		||||
 | 
			
		||||
			outState.PutString(KeyFileOrProviderKey, _keyFileOrProvider);
 | 
			
		||||
			outState.PutString(PasswordKey, _password);
 | 
			
		||||
			outState.PutStringArrayList(PendingOtpsKey, _pendingOtps);
 | 
			
		||||
			if (_otpInfo != null)
 | 
			
		||||
			{
 | 
			
		||||
				outState.PutStringArrayList(EnteredOtpsKey, GetOtpsFromUI());
 | 
			
		||||
 | 
			
		||||
				var sw = new StringWriter();
 | 
			
		||||
 | 
			
		||||
				var xws = OtpInfo.XmlWriterSettings();
 | 
			
		||||
 | 
			
		||||
				XmlWriter xw = XmlWriter.Create(sw, xws);
 | 
			
		||||
 | 
			
		||||
				XmlSerializer xs = new XmlSerializer(typeof(OtpInfo));
 | 
			
		||||
				xs.Serialize(xw, _otpInfo);
 | 
			
		||||
 | 
			
		||||
				xw.Close();
 | 
			
		||||
 | 
			
		||||
				outState.PutString(OtpInfoKey, sw.ToString());
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			//more OTP TODO:
 | 
			
		||||
			// * NfcOtp: Ask for close when db open
 | 
			
		||||
			// * Caching of aux file
 | 
			
		||||
			// *  -> implement IFileStorage in JavaFileStorage based on ListFiles
 | 
			
		||||
			// *  -> Sync
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		protected override void OnNewIntent(Intent intent)
 | 
			
		||||
@@ -896,7 +1024,7 @@ namespace keepass2android
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		private void RetrieveSettings() {
 | 
			
		||||
		private void InitializeQuickUnlockCheckbox() {
 | 
			
		||||
			CheckBox cbQuickUnlock = (CheckBox)FindViewById(Resource.Id.enable_quickunlock);
 | 
			
		||||
			cbQuickUnlock.Checked = _prefs.GetBoolean(GetString(Resource.String.QuickUnlockDefaultEnabled_key), true);
 | 
			
		||||
		}
 | 
			
		||||
@@ -909,7 +1037,7 @@ namespace keepass2android
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		private void PopulateView() {
 | 
			
		||||
		private void InitializeFilenameView() {
 | 
			
		||||
			SetEditText(Resource.Id.filename, App.Kp2a.GetFileStorage(_ioConnection).GetDisplayName(_ioConnection));
 | 
			
		||||
			if (App.Kp2a.FileDbHelper.NumberOfRecentFiles() < 2)
 | 
			
		||||
			{
 | 
			
		||||
@@ -919,8 +1047,7 @@ namespace keepass2android
 | 
			
		||||
			{
 | 
			
		||||
				FindViewById(Resource.Id.filename_group).Visibility = ViewStates.Visible;
 | 
			
		||||
			}
 | 
			
		||||
			if (KeyProviderType == KeyProviders.KeyFile)
 | 
			
		||||
				SetEditText(Resource.Id.pass_keyfile, _keyFileOrProvider);
 | 
			
		||||
			
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		protected override void OnDestroy()
 | 
			
		||||
@@ -937,15 +1064,6 @@ namespace keepass2android
 | 
			
		||||
	}
 | 
			
		||||
	*/
 | 
			
		||||
 | 
			
		||||
		private void ErrorMessage(int resId)
 | 
			
		||||
		{
 | 
			
		||||
			Toast.MakeText(this, resId, ToastLength.Long).Show();
 | 
			
		||||
		}
 | 
			
		||||
	
 | 
			
		||||
		private String GetEditText(int resId) {
 | 
			
		||||
			return Util.GetEditText(this, resId);
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		private void SetEditText(int resId, String str) {
 | 
			
		||||
			TextView te =  (TextView) FindViewById(resId);
 | 
			
		||||
			//assert(te == null);
 | 
			
		||||
@@ -1002,13 +1120,40 @@ namespace keepass2android
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
			public override void Run() {
 | 
			
		||||
 | 
			
		||||
				if (_act.KeyProviderType == KeyProviders.Otp)
 | 
			
		||||
				{
 | 
			
		||||
					try
 | 
			
		||||
					{
 | 
			
		||||
						StatusLogger.UpdateMessage(UiStringKey.SavingOtpAuxFile);
 | 
			
		||||
 | 
			
		||||
						if (!OathHotpKeyProv.CreateAuxFile(_act._otpInfo, new KeyProviderQueryContext(_act._ioConnection, false, false)))
 | 
			
		||||
							Toast.MakeText(_act, _act.GetString(Resource.String.ErrorUpdatingOtpAuxFile), ToastLength.Long).Show();
 | 
			
		||||
					}
 | 
			
		||||
					catch (Exception e)
 | 
			
		||||
					{
 | 
			
		||||
						Kp2aLog.Log(e.Message);
 | 
			
		||||
 | 
			
		||||
						Toast.MakeText(_act, _act.GetString(Resource.String.ErrorUpdatingOtpAuxFile)+" "+e.Message, ToastLength.Long).Show();
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				
 | 
			
		||||
 | 
			
		||||
				if ( Success ) 
 | 
			
		||||
				{
 | 
			
		||||
					_act.SetEditText(Resource.Id.password, "");
 | 
			
		||||
					_act.SetEditText(Resource.Id.pass_otpsecret, "");
 | 
			
		||||
					foreach (int otpId in  _act._otpTextViewIds)
 | 
			
		||||
					{
 | 
			
		||||
						_act.SetEditText(otpId, "");
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					_act.LaunchNextActivity();
 | 
			
		||||
 | 
			
		||||
					GC.Collect(); // Ensure temporary memory used while loading is collected - it will contain sensitive data such as username and password, and also the large data of the encrypted database file
 | 
			
		||||
					if ((_act.KeyProviderType == KeyProviders.Otp) || (_act.KeyProviderType == KeyProviders.OtpRecovery))
 | 
			
		||||
						App.Kp2a.GetDb().OtpAuxFileIoc = OathHotpKeyProv.GetAuxFileIoc(_act._ioConnection);
 | 
			
		||||
 | 
			
		||||
					GC.Collect(); // Ensure temporary memory used while loading is collected
 | 
			
		||||
				} 
 | 
			
		||||
				else
 | 
			
		||||
				{
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1119
									
								
								src/keepass2android/Resources/Resource.designer.cs
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1119
									
								
								src/keepass2android/Resources/Resource.designer.cs
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,188 +1,213 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    android:layout_width="fill_parent"
 | 
			
		||||
    android:layout_height="wrap_content"
 | 
			
		||||
    android:layout_marginLeft="12dip"
 | 
			
		||||
    android:layout_marginRight="12dip"
 | 
			
		||||
    android:layout_marginBottom="12dip"
 | 
			
		||||
	android:orientation="vertical"
 | 
			
		||||
			  >
 | 
			
		||||
	<RelativeLayout
 | 
			
		||||
		android:id="@+id/filename_group"
 | 
			
		||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
android:layout_width="match_parent"
 | 
			
		||||
android:layout_height="match_parent"
 | 
			
		||||
>
 | 
			
		||||
	<LinearLayout
 | 
			
		||||
		android:layout_width="fill_parent"
 | 
			
		||||
		android:layout_height="wrap_content">
 | 
			
		||||
		<TextView
 | 
			
		||||
			android:id="@+id/filename_label"
 | 
			
		||||
			android:layout_width="wrap_content"
 | 
			
		||||
			android:layout_height="wrap_content"
 | 
			
		||||
			style="@style/TextAppearance_SmallHeading"
 | 
			
		||||
			android:text="@string/pass_filename" />
 | 
			
		||||
		<ImageView
 | 
			
		||||
			android:id="@+id/divider1"
 | 
			
		||||
			android:layout_width="fill_parent"
 | 
			
		||||
			android:layout_height="wrap_content"
 | 
			
		||||
			android:layout_below="@id/filename_label"
 | 
			
		||||
			android:scaleType="fitXY"
 | 
			
		||||
			android:src="@android:drawable/divider_horizontal_dark" />
 | 
			
		||||
		<HorizontalScrollView
 | 
			
		||||
			android:id="@+id/filenamescroll"
 | 
			
		||||
			android:layout_width="fill_parent"
 | 
			
		||||
			android:layout_height="wrap_content"
 | 
			
		||||
			android:layout_below="@id/divider1">
 | 
			
		||||
			<TextView
 | 
			
		||||
				android:id="@+id/filename"
 | 
			
		||||
				style="@style/GroupText"
 | 
			
		||||
				android:layout_width="fill_parent"
 | 
			
		||||
				android:layout_height="wrap_content"
 | 
			
		||||
				android:singleLine="true"
 | 
			
		||||
				android:ellipsize="none"
 | 
			
		||||
				android:focusable="true"
 | 
			
		||||
				android:focusableInTouchMode="true" />
 | 
			
		||||
		</HorizontalScrollView>
 | 
			
		||||
		<ImageView
 | 
			
		||||
			android:id="@+id/divider2"
 | 
			
		||||
			android:layout_width="fill_parent"
 | 
			
		||||
			android:layout_height="wrap_content"
 | 
			
		||||
			android:layout_below="@id/filenamescroll"
 | 
			
		||||
			android:scaleType="fitXY"
 | 
			
		||||
			android:src="@android:drawable/divider_horizontal_dark" />
 | 
			
		||||
	</RelativeLayout>
 | 
			
		||||
    <TextView
 | 
			
		||||
        android:id="@+id/password_label"
 | 
			
		||||
        android:layout_width="wrap_content"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:text="" />
 | 
			
		||||
	<Spinner
 | 
			
		||||
    android:id="@+id/password_mode_spinner"
 | 
			
		||||
    android:layout_width="fill_parent"
 | 
			
		||||
    android:layout_height="wrap_content" 
 | 
			
		||||
	android:entries="@array/password_modes"
 | 
			
		||||
	/>
 | 
			
		||||
 | 
			
		||||
	<LinearLayout
 | 
			
		||||
        android:id="@+id/passwordLine"
 | 
			
		||||
        android:layout_width="fill_parent"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:orientation="horizontal">
 | 
			
		||||
        <EditText
 | 
			
		||||
            android:id="@+id/password"
 | 
			
		||||
            android:layout_width="0px"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:singleLine="true"
 | 
			
		||||
            android:inputType="textPassword"
 | 
			
		||||
            android:layout_weight="1"
 | 
			
		||||
            android:hint="@string/hint_login_pass" />
 | 
			
		||||
        <ImageButton
 | 
			
		||||
            android:id="@+id/toggle_password"
 | 
			
		||||
            android:layout_width="wrap_content"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:src="@drawable/ic_menu_view" />
 | 
			
		||||
    </LinearLayout>
 | 
			
		||||
	<LinearLayout
 | 
			
		||||
        android:id="@+id/keyfileLine"
 | 
			
		||||
        android:layout_width="fill_parent"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:orientation="horizontal">
 | 
			
		||||
        <EditText
 | 
			
		||||
            android:id="@+id/pass_keyfile"
 | 
			
		||||
            android:layout_width="0px"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:singleLine="true"
 | 
			
		||||
            android:layout_weight="1"
 | 
			
		||||
            android:hint="@string/entry_keyfile" />
 | 
			
		||||
        <ImageButton
 | 
			
		||||
            android:id="@+id/browse_button"
 | 
			
		||||
            android:layout_width="wrap_content"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:src="@drawable/ic_launcher_folder_small" />
 | 
			
		||||
    </LinearLayout>
 | 
			
		||||
	<LinearLayout
 | 
			
		||||
        android:id="@+id/otpView"
 | 
			
		||||
		android:layout_height="match_parent"
 | 
			
		||||
		android:layout_marginLeft="12dip"
 | 
			
		||||
		android:layout_marginRight="12dip"
 | 
			
		||||
        android:layout_width="fill_parent"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
		android:layout_marginBottom="12dip"
 | 
			
		||||
		android:orientation="vertical"
 | 
			
		||||
		>
 | 
			
		||||
		<LinearLayout
 | 
			
		||||
        android:id="@+id/otpInitView"
 | 
			
		||||
			  >
 | 
			
		||||
		<RelativeLayout
 | 
			
		||||
			android:id="@+id/filename_group"
 | 
			
		||||
			android:layout_width="fill_parent"
 | 
			
		||||
			android:layout_height="wrap_content">
 | 
			
		||||
			<TextView
 | 
			
		||||
				android:id="@+id/filename_label"
 | 
			
		||||
				android:layout_width="wrap_content"
 | 
			
		||||
				android:layout_height="wrap_content"
 | 
			
		||||
				style="@style/TextAppearance_SmallHeading"
 | 
			
		||||
				android:text="@string/pass_filename" />
 | 
			
		||||
			<ImageView
 | 
			
		||||
				android:id="@+id/divider1"
 | 
			
		||||
				android:layout_width="fill_parent"
 | 
			
		||||
				android:layout_height="wrap_content"
 | 
			
		||||
				android:layout_below="@id/filename_label"
 | 
			
		||||
				android:scaleType="fitXY"
 | 
			
		||||
				android:src="@android:drawable/divider_horizontal_dark" />
 | 
			
		||||
			<HorizontalScrollView
 | 
			
		||||
				android:id="@+id/filenamescroll"
 | 
			
		||||
				android:layout_width="fill_parent"
 | 
			
		||||
				android:layout_height="wrap_content"
 | 
			
		||||
				android:layout_below="@id/divider1">
 | 
			
		||||
				<TextView
 | 
			
		||||
					android:id="@+id/filename"
 | 
			
		||||
					style="@style/GroupText"
 | 
			
		||||
					android:layout_width="fill_parent"
 | 
			
		||||
					android:layout_height="wrap_content"
 | 
			
		||||
					android:singleLine="true"
 | 
			
		||||
					android:ellipsize="none"
 | 
			
		||||
					android:focusable="true"
 | 
			
		||||
					android:focusableInTouchMode="true" />
 | 
			
		||||
			</HorizontalScrollView>
 | 
			
		||||
			<ImageView
 | 
			
		||||
				android:id="@+id/divider2"
 | 
			
		||||
				android:layout_width="fill_parent"
 | 
			
		||||
				android:layout_height="wrap_content"
 | 
			
		||||
				android:layout_below="@id/filenamescroll"
 | 
			
		||||
				android:scaleType="fitXY"
 | 
			
		||||
				android:src="@android:drawable/divider_horizontal_dark" />
 | 
			
		||||
		</RelativeLayout>
 | 
			
		||||
		<TextView
 | 
			
		||||
			android:id="@+id/password_label"
 | 
			
		||||
			android:layout_width="wrap_content"
 | 
			
		||||
			android:layout_height="wrap_content"
 | 
			
		||||
			android:text="@string/master_key_type" />
 | 
			
		||||
		<Spinner
 | 
			
		||||
		android:id="@+id/password_mode_spinner"
 | 
			
		||||
		android:layout_width="fill_parent"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
		>
 | 
			
		||||
			<Button
 | 
			
		||||
			android:id="@+id/init_otp"
 | 
			
		||||
			android:text="@string/init_otp"
 | 
			
		||||
			android:layout_width="wrap_content"
 | 
			
		||||
			android:layout_height="wrap_content"/>
 | 
			
		||||
			<TextView
 | 
			
		||||
				android:id="@+id/otps_pending"
 | 
			
		||||
			android:text="@string/otps_pending"
 | 
			
		||||
			android:layout_width="wrap_content"
 | 
			
		||||
			android:layout_height="wrap_content" />
 | 
			
		||||
		android:layout_height="wrap_content"
 | 
			
		||||
		android:entries="@array/password_modes"
 | 
			
		||||
	/>
 | 
			
		||||
 | 
			
		||||
		<LinearLayout
 | 
			
		||||
			android:id="@+id/passwordLine"
 | 
			
		||||
			android:layout_width="fill_parent"
 | 
			
		||||
			android:layout_height="wrap_content"
 | 
			
		||||
			android:orientation="horizontal">
 | 
			
		||||
			<EditText
 | 
			
		||||
				android:id="@+id/password"
 | 
			
		||||
				android:layout_width="0px"
 | 
			
		||||
				android:layout_height="wrap_content"
 | 
			
		||||
				android:singleLine="true"
 | 
			
		||||
				android:inputType="textPassword"
 | 
			
		||||
				android:layout_weight="1"
 | 
			
		||||
				android:hint="@string/hint_login_pass" />
 | 
			
		||||
			<ImageButton
 | 
			
		||||
				android:id="@+id/toggle_password"
 | 
			
		||||
				android:layout_width="wrap_content"
 | 
			
		||||
				android:layout_height="wrap_content"
 | 
			
		||||
				android:src="@drawable/ic_menu_view" />
 | 
			
		||||
		</LinearLayout>
 | 
			
		||||
		<LinearLayout
 | 
			
		||||
        android:id="@+id/otpEntry"
 | 
			
		||||
        android:layout_width="fill_parent"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
		android:visibility="gone"
 | 
			
		||||
		android:orientation="vertical"
 | 
			
		||||
			android:id="@+id/keyfileLine"
 | 
			
		||||
			android:layout_width="fill_parent"
 | 
			
		||||
			android:layout_height="wrap_content"
 | 
			
		||||
			android:orientation="horizontal">
 | 
			
		||||
			<EditText
 | 
			
		||||
				android:id="@+id/pass_keyfile"
 | 
			
		||||
				android:layout_width="0px"
 | 
			
		||||
				android:layout_height="wrap_content"
 | 
			
		||||
				android:singleLine="true"
 | 
			
		||||
				android:layout_weight="1"
 | 
			
		||||
				android:hint="@string/entry_keyfile" />
 | 
			
		||||
			<ImageButton
 | 
			
		||||
				android:id="@+id/browse_button"
 | 
			
		||||
				android:layout_width="wrap_content"
 | 
			
		||||
				android:layout_height="wrap_content"
 | 
			
		||||
				android:src="@drawable/ic_launcher_folder_small" />
 | 
			
		||||
		</LinearLayout>
 | 
			
		||||
		<LinearLayout
 | 
			
		||||
			android:id="@+id/otpView"
 | 
			
		||||
			android:layout_marginLeft="12dip"
 | 
			
		||||
			android:layout_marginRight="12dip"
 | 
			
		||||
			android:layout_width="fill_parent"
 | 
			
		||||
			android:layout_height="wrap_content"
 | 
			
		||||
			android:orientation="vertical"
 | 
			
		||||
		>
 | 
			
		||||
			<TextView
 | 
			
		||||
        android:id="@+id/otp_expl"
 | 
			
		||||
        android:layout_width="wrap_content"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:text="@string/otp_explanation" />
 | 
			
		||||
			<LinearLayout
 | 
			
		||||
			android:id="@+id/otpInitView"
 | 
			
		||||
			android:layout_width="fill_parent"
 | 
			
		||||
			android:layout_height="wrap_content"
 | 
			
		||||
			android:orientation="vertical"
 | 
			
		||||
		>
 | 
			
		||||
				<Button
 | 
			
		||||
				android:id="@+id/init_otp"
 | 
			
		||||
				android:text="@string/init_otp"
 | 
			
		||||
				android:layout_width="wrap_content"
 | 
			
		||||
				android:layout_height="wrap_content"/>
 | 
			
		||||
				<TextView
 | 
			
		||||
					android:id="@+id/otps_pending"
 | 
			
		||||
				android:text="@string/otps_pending"
 | 
			
		||||
				android:layout_width="wrap_content"
 | 
			
		||||
				android:layout_height="wrap_content" />
 | 
			
		||||
			</LinearLayout>
 | 
			
		||||
			<LinearLayout
 | 
			
		||||
			android:id="@+id/otpEntry"
 | 
			
		||||
			android:layout_width="fill_parent"
 | 
			
		||||
			android:layout_height="wrap_content"
 | 
			
		||||
			android:visibility="gone"
 | 
			
		||||
			android:orientation="vertical"
 | 
			
		||||
		>
 | 
			
		||||
				<TextView
 | 
			
		||||
			android:id="@+id/otp_expl"
 | 
			
		||||
			android:layout_width="wrap_content"
 | 
			
		||||
			android:layout_height="wrap_content"
 | 
			
		||||
			android:text="@string/otp_explanation" />
 | 
			
		||||
 | 
			
		||||
			<EditText
 | 
			
		||||
						android:id="@+id/otp1"
 | 
			
		||||
						android:layout_width="fill_parent"
 | 
			
		||||
						android:layout_height="wrap_content"
 | 
			
		||||
						android:text="93317749"
 | 
			
		||||
						android:singleLine="true" />
 | 
			
		||||
			<EditText
 | 
			
		||||
						android:id="@+id/otp2"
 | 
			
		||||
						android:text="54719327"
 | 
			
		||||
						android:layout_width="fill_parent"
 | 
			
		||||
						android:layout_height="wrap_content"
 | 
			
		||||
						android:singleLine="true" />
 | 
			
		||||
			<EditText
 | 
			
		||||
						android:id="@+id/otp3"
 | 
			
		||||
						android:text="49844651"
 | 
			
		||||
						android:layout_width="fill_parent"
 | 
			
		||||
						android:layout_height="wrap_content"
 | 
			
		||||
						android:singleLine="true" />
 | 
			
		||||
			<EditText
 | 
			
		||||
						android:id="@+id/otp4"
 | 
			
		||||
						android:layout_width="fill_parent"
 | 
			
		||||
						android:layout_height="wrap_content"
 | 
			
		||||
						android:singleLine="true" />
 | 
			
		||||
			<EditText
 | 
			
		||||
						android:id="@+id/otp5"
 | 
			
		||||
						android:layout_width="fill_parent"
 | 
			
		||||
						android:layout_height="wrap_content"
 | 
			
		||||
						android:singleLine="true" />
 | 
			
		||||
			<EditText
 | 
			
		||||
						android:id="@+id/otp6"
 | 
			
		||||
						android:layout_width="fill_parent"
 | 
			
		||||
						android:layout_height="wrap_content"
 | 
			
		||||
						android:singleLine="true" />
 | 
			
		||||
				<EditText
 | 
			
		||||
							android:id="@+id/otp1"
 | 
			
		||||
							android:layout_width="fill_parent"
 | 
			
		||||
							android:layout_height="wrap_content"
 | 
			
		||||
							android:text="93317749"
 | 
			
		||||
							android:singleLine="true" />
 | 
			
		||||
				<EditText
 | 
			
		||||
							android:id="@+id/otp2"
 | 
			
		||||
							android:text="54719327"
 | 
			
		||||
							android:layout_width="fill_parent"
 | 
			
		||||
							android:layout_height="wrap_content"
 | 
			
		||||
							android:singleLine="true" />
 | 
			
		||||
				<EditText
 | 
			
		||||
							android:id="@+id/otp3"
 | 
			
		||||
							android:text="49844651"
 | 
			
		||||
							android:layout_width="fill_parent"
 | 
			
		||||
							android:layout_height="wrap_content"
 | 
			
		||||
							android:singleLine="true" />
 | 
			
		||||
				<EditText
 | 
			
		||||
							android:id="@+id/otp4"
 | 
			
		||||
							android:layout_width="fill_parent"
 | 
			
		||||
							android:layout_height="wrap_content"
 | 
			
		||||
							android:singleLine="true" />
 | 
			
		||||
				<EditText
 | 
			
		||||
							android:id="@+id/otp5"
 | 
			
		||||
							android:layout_width="fill_parent"
 | 
			
		||||
							android:layout_height="wrap_content"
 | 
			
		||||
							android:singleLine="true" />
 | 
			
		||||
				<EditText
 | 
			
		||||
							android:id="@+id/otp6"
 | 
			
		||||
							android:layout_width="fill_parent"
 | 
			
		||||
							android:layout_height="wrap_content"
 | 
			
		||||
							android:singleLine="true" />
 | 
			
		||||
 | 
			
		||||
			</LinearLayout>
 | 
			
		||||
 | 
			
		||||
		</LinearLayout>
 | 
			
		||||
		
 | 
			
		||||
		<LinearLayout
 | 
			
		||||
			android:id="@+id/otpSecretLine"
 | 
			
		||||
			android:layout_width="fill_parent"
 | 
			
		||||
			android:layout_height="wrap_content"
 | 
			
		||||
			android:orientation="horizontal">
 | 
			
		||||
			<EditText
 | 
			
		||||
				android:id="@+id/pass_otpsecret"
 | 
			
		||||
				android:layout_width="0px"
 | 
			
		||||
				android:layout_height="wrap_content"
 | 
			
		||||
				android:singleLine="true"
 | 
			
		||||
				android:layout_weight="1"
 | 
			
		||||
				android:hint="@string/otpsecret_hint" />
 | 
			
		||||
			<Spinner
 | 
			
		||||
		android:id="@+id/otpsecret_format_spinner"
 | 
			
		||||
		android:layout_width="wrap_content"
 | 
			
		||||
		android:layout_height="wrap_content"
 | 
			
		||||
		/>
 | 
			
		||||
		</LinearLayout>
 | 
			
		||||
		<Button
 | 
			
		||||
			android:id="@+id/pass_ok"
 | 
			
		||||
			android:text="@android:string/ok"
 | 
			
		||||
			android:layout_width="fill_parent"
 | 
			
		||||
			android:layout_height="wrap_content"/>
 | 
			
		||||
		<Button
 | 
			
		||||
			android:id="@+id/kill_app"
 | 
			
		||||
			android:text="@string/kill_app_label"
 | 
			
		||||
			android:layout_width="fill_parent"
 | 
			
		||||
			android:layout_height="wrap_content" />
 | 
			
		||||
		<CheckBox
 | 
			
		||||
			android:id="@+id/enable_quickunlock"
 | 
			
		||||
			android:layout_width="wrap_content"
 | 
			
		||||
			android:layout_height="wrap_content"
 | 
			
		||||
			android:text="@string/enable_quickunlock" />
 | 
			
		||||
	</LinearLayout>
 | 
			
		||||
	<Button
 | 
			
		||||
        android:id="@+id/pass_ok"
 | 
			
		||||
        android:text="@android:string/ok"
 | 
			
		||||
        android:layout_width="fill_parent"
 | 
			
		||||
        android:layout_height="wrap_content"/>
 | 
			
		||||
	<Button
 | 
			
		||||
        android:id="@+id/kill_app"
 | 
			
		||||
        android:text="@string/kill_app_label"
 | 
			
		||||
        android:layout_width="fill_parent"
 | 
			
		||||
        android:layout_height="wrap_content" />
 | 
			
		||||
    <CheckBox
 | 
			
		||||
        android:id="@+id/enable_quickunlock"
 | 
			
		||||
        android:layout_width="wrap_content"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:text="@string/enable_quickunlock" />
 | 
			
		||||
</LinearLayout>
 | 
			
		||||
</ScrollView>
 | 
			
		||||
@@ -143,6 +143,7 @@
 | 
			
		||||
  <string name="omitbackup_summary">Omit \'Backup\' and Recycle Bin group from search results</string>
 | 
			
		||||
  <string name="pass_filename">KeePass database filename</string>
 | 
			
		||||
  <string name="password_title">Enter database password</string>
 | 
			
		||||
  <string name="master_key_type">Select master key type:</string>
 | 
			
		||||
  <string name="progress_create">Creating new database…</string>
 | 
			
		||||
  <string name="create_database">Create database</string>
 | 
			
		||||
  <string name="progress_title">Working…</string>
 | 
			
		||||
@@ -299,9 +300,17 @@
 | 
			
		||||
	<string name="UpdatedRemoteFileOnLoad">Updated remote file.</string>
 | 
			
		||||
	<string name="NotifyOpenFromLocalDueToConflict">Opened local file due to conflict with changes in remote file. Use Synchronize menu to merge.</string>
 | 
			
		||||
	<string name="LoadedFromRemoteInSync">Remote file and cache are synchronized.</string>
 | 
			
		||||
	<string name="UpdatedCachedFileOnLoad">Updated local cache copy of database.</string>
 | 
			
		||||
	<string name="UpdatedCachedFileOnLoad">Updated local cache copy of %1$s.</string>
 | 
			
		||||
	<string name="RemoteDatabaseUnchanged">No changes detected.</string>
 | 
			
		||||
 | 
			
		||||
	<string name="ResolvedCacheConflictByUsingRemoteOtpAux">Updated cached OTP auxiliary file: Remote counter was higher.</string>
 | 
			
		||||
	<string name="ResolvedCacheConflictByUsingLocalOtpAux">Updated remote OTP auxiliary file: Local counter was higher.</string>
 | 
			
		||||
 | 
			
		||||
	<string name="SynchronizingOtpAuxFile">Synchronizing OTP auxiliary file…</string>
 | 
			
		||||
	
 | 
			
		||||
	<string name="database_file">database file</string>
 | 
			
		||||
	<string name="otp_aux_file">OTP auxiliary file</string>
 | 
			
		||||
 | 
			
		||||
	<string name="ErrorOcurred">An error occured:</string>
 | 
			
		||||
 | 
			
		||||
	<string name="synchronize_database_menu">Synchronize database…</string>
 | 
			
		||||
@@ -343,14 +352,19 @@
 | 
			
		||||
 | 
			
		||||
	<string name="error_adding_keyfile">Error while adding the keyfile!</string>
 | 
			
		||||
 | 
			
		||||
	<string name="init_otp">Enter OTPs…</string>
 | 
			
		||||
	<string name="init_otp">Load OTP auxiliary file…</string>
 | 
			
		||||
	<string name="otp_explanation">Enter the next One-time-passwords (OTPs). Swipe your Yubikey NEO at the back of your device to enter via NFC.</string>
 | 
			
		||||
	<string name="otp_hint">OTP %1$d</string>
 | 
			
		||||
	<string name="CouldntLoadOtpAuxFile">Could not load auxiliary OTP file!</string>
 | 
			
		||||
	<string name="otp_discarded_because_no_db">Please select database first. OTP is discarded for security reasons.</string>
 | 
			
		||||
	<string name="otp_discarded_no_space">OTP discarded: All OTPs already entered!</string>
 | 
			
		||||
	<string name="otp_discarded_because_db_open">Please close database first. OTP is discarded.</string>
 | 
			
		||||
	<string name="otps_pending">(One or more OTPs already available)</string>
 | 
			
		||||
 | 
			
		||||
	<string name="otpsecret_hint">OTP secret (e.g. 01 23 ab cd…)</string>
 | 
			
		||||
	<string name="CouldntParseOtpSecret">Error parsing OTP secret!</string>
 | 
			
		||||
	<string name="OtpKeyError">Failed to create OTP key! Make sure you have entered the correct OTPs.</string>
 | 
			
		||||
	<string name="ErrorUpdatingOtpAuxFile">Error updating OTP auxiliary file!</string>
 | 
			
		||||
	<string name="SavingOtpAuxFile">Saving auxiliary OTP file…</string>
 | 
			
		||||
 | 
			
		||||
	<string name="loading">Loading…</string>
 | 
			
		||||
	
 | 
			
		||||
@@ -477,5 +491,6 @@ Initial public release
 | 
			
		||||
		<item>Password only</item>
 | 
			
		||||
		<item>Password + Key file</item>
 | 
			
		||||
		<item>Password + OTP</item>
 | 
			
		||||
		<item>Password + OTP secret (recovery mode)</item>
 | 
			
		||||
	</string-array>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -49,13 +49,19 @@ namespace OtpKeyProv
 | 
			
		||||
		private static IOConnectionInfo GetAuxFileIoc(KeyProviderQueryContext ctx)
 | 
			
		||||
		{
 | 
			
		||||
			IOConnectionInfo ioc = ctx.DatabaseIOInfo.CloneDeep();
 | 
			
		||||
			IFileStorage fileStorage = App.Kp2a.GetOtpAuxFileStorage(ioc);
 | 
			
		||||
			IOConnectionInfo iocAux = fileStorage.GetFilePath(fileStorage.GetParentPath(ioc),
 | 
			
		||||
			                                                  fileStorage.GetFilenameWithoutPathAndExt(ioc) + AuxFileExt);
 | 
			
		||||
			var iocAux = GetAuxFileIoc(ioc);
 | 
			
		||||
 | 
			
		||||
			return iocAux;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static IOConnectionInfo GetAuxFileIoc(IOConnectionInfo databaseIoc)
 | 
			
		||||
		{
 | 
			
		||||
			IFileStorage fileStorage = App.Kp2a.GetOtpAuxFileStorage(databaseIoc);
 | 
			
		||||
			IOConnectionInfo iocAux = fileStorage.GetFilePath(fileStorage.GetParentPath(databaseIoc),
 | 
			
		||||
			                                                  fileStorage.GetFilenameWithoutPathAndExt(databaseIoc) + AuxFileExt);
 | 
			
		||||
			return iocAux;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static OtpInfo LoadOtpInfo(KeyProviderQueryContext ctx)
 | 
			
		||||
		{
 | 
			
		||||
			return OtpInfo.Load(GetAuxFileIoc(ctx));
 | 
			
		||||
@@ -130,7 +136,7 @@ namespace OtpKeyProv
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		private static bool CreateAuxFile(OtpInfo otpInfo,
 | 
			
		||||
		public static bool CreateAuxFile(OtpInfo otpInfo,
 | 
			
		||||
			KeyProviderQueryContext ctx)
 | 
			
		||||
		{
 | 
			
		||||
			otpInfo.Type = ProvType;
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,70 @@
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Xml.Serialization;
 | 
			
		||||
using KeePassLib.Serialization;
 | 
			
		||||
using OtpKeyProv;
 | 
			
		||||
using keepass2android.Io;
 | 
			
		||||
 | 
			
		||||
namespace keepass2android.addons.OtpKeyProv
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Class which provides caching for OtpInfo-files. This is an extension to CachingFileStorage required to handle conflicts directly when loading.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	class OtpAuxCachingFileStorage: CachingFileStorage
 | 
			
		||||
	{
 | 
			
		||||
		private readonly IOtpAuxCacheSupervisor _cacheSupervisor;
 | 
			
		||||
 | 
			
		||||
		internal interface IOtpAuxCacheSupervisor: ICacheSupervisor
 | 
			
		||||
		{
 | 
			
		||||
			/// <summary>
 | 
			
		||||
			/// called when there was a conflict which was resolved by using the remote file.
 | 
			
		||||
			/// </summary>
 | 
			
		||||
			void ResolvedCacheConflictByUsingRemote(IOConnectionInfo ioc);
 | 
			
		||||
 | 
			
		||||
			/// <summary>
 | 
			
		||||
			/// called when there was a conflict which was resolved by using the local file.
 | 
			
		||||
			/// </summary>
 | 
			
		||||
			void ResolvedCacheConflictByUsingLocal(IOConnectionInfo ioc);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		public OtpAuxCachingFileStorage(IFileStorage cachedStorage, string cacheDir, IOtpAuxCacheSupervisor cacheSupervisor)
 | 
			
		||||
			: base(cachedStorage, cacheDir, cacheSupervisor)
 | 
			
		||||
		{
 | 
			
		||||
			_cacheSupervisor = cacheSupervisor;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		protected override Stream OpenFileForReadWithConflict(IOConnectionInfo ioc, string cachedFilePath)
 | 
			
		||||
		{
 | 
			
		||||
			OtpInfo remoteOtpInfo, localOtpInfo;
 | 
			
		||||
			//load both files
 | 
			
		||||
			XmlSerializer xs = new XmlSerializer(typeof (OtpInfo));
 | 
			
		||||
			localOtpInfo = (OtpInfo) xs.Deserialize(File.OpenRead(cachedFilePath));
 | 
			
		||||
			using (Stream remoteStream = _cachedStorage.OpenFileForRead(ioc))
 | 
			
		||||
			{
 | 
			
		||||
				remoteOtpInfo = (OtpInfo) xs.Deserialize(remoteStream);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			//see which OtpInfo has the bigger Counter value and use this one:
 | 
			
		||||
			if (localOtpInfo.Counter > remoteOtpInfo.Counter)
 | 
			
		||||
			{
 | 
			
		||||
				//overwrite the remote file
 | 
			
		||||
				UpdateRemoteFile(File.OpenRead(cachedFilePath), 
 | 
			
		||||
				                 ioc, 
 | 
			
		||||
				                 App.Kp2a.GetBooleanPreference(PreferenceKey.UseFileTransactions),
 | 
			
		||||
				                 GetBaseVersionHash(ioc)
 | 
			
		||||
					);
 | 
			
		||||
 | 
			
		||||
				_cacheSupervisor.ResolvedCacheConflictByUsingRemote(ioc);
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				//overwrite the local file:
 | 
			
		||||
				UpdateCacheFromRemote(ioc, cachedFilePath);
 | 
			
		||||
				_cacheSupervisor.ResolvedCacheConflictByUsingLocal(ioc);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			//now return the local file in any way:
 | 
			
		||||
			return File.OpenRead(cachedFilePath);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -197,18 +197,8 @@ namespace OtpKeyProv
 | 
			
		||||
				using (var trans = App.Kp2a.GetOtpAuxFileStorage(ioc)
 | 
			
		||||
					               .OpenWriteTransaction(ioc, App.Kp2a.GetBooleanPreference(PreferenceKey.UseFileTransactions)))
 | 
			
		||||
				{
 | 
			
		||||
					XmlWriterSettings xws = new XmlWriterSettings();
 | 
			
		||||
					xws.CloseOutput = true;
 | 
			
		||||
					xws.Encoding = StrUtil.Utf8;
 | 
			
		||||
					xws.Indent = true;
 | 
			
		||||
					xws.IndentChars = "\t";
 | 
			
		||||
 | 
			
		||||
					XmlWriter xw = XmlWriter.Create(trans.OpenFile(), xws);
 | 
			
		||||
 | 
			
		||||
					XmlSerializer xs = new XmlSerializer(typeof (OtpInfo));
 | 
			
		||||
					xs.Serialize(xw, otpInfo);
 | 
			
		||||
 | 
			
		||||
					xw.Close();
 | 
			
		||||
					var stream = trans.OpenFile();
 | 
			
		||||
					WriteToStream(otpInfo, stream);
 | 
			
		||||
					trans.CommitWrite();
 | 
			
		||||
				}
 | 
			
		||||
				return true;
 | 
			
		||||
@@ -222,6 +212,31 @@ namespace OtpKeyProv
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		public static void WriteToStream(OtpInfo otpInfo, Stream stream)
 | 
			
		||||
		{
 | 
			
		||||
			var xws = XmlWriterSettings();
 | 
			
		||||
 | 
			
		||||
			XmlWriter xw = XmlWriter.Create(stream, xws);
 | 
			
		||||
 | 
			
		||||
			XmlSerializer xs = new XmlSerializer(typeof (OtpInfo));
 | 
			
		||||
			xs.Serialize(xw, otpInfo);
 | 
			
		||||
 | 
			
		||||
			xw.Close();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static XmlWriterSettings XmlWriterSettings()
 | 
			
		||||
		{
 | 
			
		||||
			XmlWriterSettings xws = new XmlWriterSettings
 | 
			
		||||
				{
 | 
			
		||||
					CloseOutput = true,
 | 
			
		||||
					Encoding = StrUtil.Utf8,
 | 
			
		||||
					Indent = true,
 | 
			
		||||
					IndentChars = "\t"
 | 
			
		||||
				};
 | 
			
		||||
			return xws;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void EncryptSecret()
 | 
			
		||||
		{
 | 
			
		||||
			if(m_pbSecret == null) throw new InvalidOperationException();
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,7 @@ using Android.Preferences;
 | 
			
		||||
using TwofishCipher;
 | 
			
		||||
#endif
 | 
			
		||||
using keepass2android.Io;
 | 
			
		||||
using keepass2android.addons.OtpKeyProv;
 | 
			
		||||
 | 
			
		||||
namespace keepass2android
 | 
			
		||||
{
 | 
			
		||||
@@ -294,8 +295,11 @@ namespace keepass2android
 | 
			
		||||
 | 
			
		||||
					builder.SetNegativeButton(GetResourceString(noString), noHandler);
 | 
			
		||||
 | 
			
		||||
					builder.SetNeutralButton(ctx.GetString(Android.Resource.String.Cancel),
 | 
			
		||||
											 cancelHandler);
 | 
			
		||||
					if (cancelHandler != null)
 | 
			
		||||
					{
 | 
			
		||||
						builder.SetNeutralButton(ctx.GetString(Android.Resource.String.Cancel),
 | 
			
		||||
												 cancelHandler);	
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					Dialog dialog = builder.Create();
 | 
			
		||||
					dialog.Show();
 | 
			
		||||
@@ -447,7 +451,7 @@ namespace keepass2android
 | 
			
		||||
            return _db;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
		void ShowToast(string message)
 | 
			
		||||
		internal void ShowToast(string message)
 | 
			
		||||
		{
 | 
			
		||||
			var handler = new Handler(Looper.MainLooper);
 | 
			
		||||
			handler.Post(() => { Toast.MakeText(Application.Context, message, ToastLength.Long).Show(); });
 | 
			
		||||
@@ -466,7 +470,8 @@ namespace keepass2android
 | 
			
		||||
 | 
			
		||||
		public void UpdatedCachedFileOnLoad(IOConnectionInfo ioc)
 | 
			
		||||
		{
 | 
			
		||||
			ShowToast(Application.Context.GetString(Resource.String.UpdatedCachedFileOnLoad));
 | 
			
		||||
			ShowToast(Application.Context.GetString(Resource.String.UpdatedCachedFileOnLoad, 
 | 
			
		||||
				new Java.Lang.Object[] { Application.Context.GetString(Resource.String.database_file) }));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void UpdatedRemoteFileOnLoad(IOConnectionInfo ioc)
 | 
			
		||||
@@ -510,7 +515,7 @@ namespace keepass2android
 | 
			
		||||
				
 | 
			
		||||
				if (DatabaseCacheEnabled)
 | 
			
		||||
				{
 | 
			
		||||
					return new CachingFileStorage(innerFileStorage, Application.Context.CacheDir.Path, this);
 | 
			
		||||
					return new OtpAuxCachingFileStorage(innerFileStorage, Application.Context.CacheDir.Path, new OtpAuxCacheSupervisor(this));
 | 
			
		||||
				}
 | 
			
		||||
				else
 | 
			
		||||
				{
 | 
			
		||||
@@ -532,7 +537,7 @@ namespace keepass2android
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///Application class for Keepass2Android: Contains static Database variable to be used by all components.
 | 
			
		||||
	///Application class for Keepass2Android: Contains static Database variable to be used by all components.
 | 
			
		||||
#if NoNet
 | 
			
		||||
	[Application(Debuggable=false, Label=AppNames.AppName)]
 | 
			
		||||
#else
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										59
									
								
								src/keepass2android/app/OtpAuxCacheSupervisor.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/keepass2android/app/OtpAuxCacheSupervisor.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,59 @@
 | 
			
		||||
using System;
 | 
			
		||||
using Android.App;
 | 
			
		||||
using KeePassLib.Serialization;
 | 
			
		||||
using keepass2android.addons.OtpKeyProv;
 | 
			
		||||
 | 
			
		||||
namespace keepass2android
 | 
			
		||||
{
 | 
			
		||||
	public class OtpAuxCacheSupervisor : OtpAuxCachingFileStorage.IOtpAuxCacheSupervisor
 | 
			
		||||
	{
 | 
			
		||||
		private readonly Kp2aApp _app;
 | 
			
		||||
 | 
			
		||||
		public OtpAuxCacheSupervisor(Kp2aApp app)
 | 
			
		||||
		{
 | 
			
		||||
			_app = app;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void CouldntSaveToRemote(IOConnectionInfo ioc, Exception ex)
 | 
			
		||||
		{
 | 
			
		||||
			_app.CouldntSaveToRemote(ioc, ex);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void CouldntOpenFromRemote(IOConnectionInfo ioc, Exception ex)
 | 
			
		||||
		{
 | 
			
		||||
			_app.CouldntOpenFromRemote(ioc, ex);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void UpdatedCachedFileOnLoad(IOConnectionInfo ioc)
 | 
			
		||||
		{
 | 
			
		||||
			_app.ShowToast(Application.Context.GetString(Resource.String.UpdatedCachedFileOnLoad,
 | 
			
		||||
			                                             new Java.Lang.Object[] { Application.Context.GetString(Resource.String.otp_aux_file) }));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void UpdatedRemoteFileOnLoad(IOConnectionInfo ioc)
 | 
			
		||||
		{
 | 
			
		||||
			_app.UpdatedRemoteFileOnLoad(ioc);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void NotifyOpenFromLocalDueToConflict(IOConnectionInfo ioc)
 | 
			
		||||
		{
 | 
			
		||||
			//must not be called . Conflicts should be resolved.
 | 
			
		||||
			throw new InvalidOperationException();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void LoadedFromRemoteInSync(IOConnectionInfo ioc)
 | 
			
		||||
		{
 | 
			
		||||
			_app.LoadedFromRemoteInSync(ioc);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void ResolvedCacheConflictByUsingRemote(IOConnectionInfo ioc)
 | 
			
		||||
		{
 | 
			
		||||
			_app.ShowToast(Application.Context.GetString(Resource.String.ResolvedCacheConflictByUsingRemoteOtpAux));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void ResolvedCacheConflictByUsingLocal(IOConnectionInfo ioc)
 | 
			
		||||
		{
 | 
			
		||||
			_app.ShowToast(Application.Context.GetString(Resource.String.ResolvedCacheConflictByUsingLocalOtpAux));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -373,7 +373,7 @@ namespace keepass2android
 | 
			
		||||
#if !EXCLUDE_FILECHOOSER
 | 
			
		||||
				StartFileChooser(ioc.Path);
 | 
			
		||||
#else
 | 
			
		||||
				LaunchPasswordActivityForIoc(new IOConnectionInfo { Path = "/mnt/sdcard/keepass/yubi2.kdbx"});
 | 
			
		||||
				LaunchPasswordActivityForIoc(new IOConnectionInfo { Path = "/mnt/sdcard/keepass/yubi.kdbx"});
 | 
			
		||||
#endif
 | 
			
		||||
			}
 | 
			
		||||
			if ((resultCode == Result.Canceled) && (data != null) && (data.HasExtra("EXTRA_ERROR_MESSAGE")))
 | 
			
		||||
 
 | 
			
		||||
@@ -84,9 +84,11 @@
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <Compile Include="addons\OtpKeyProv\EncodingUtil.cs" />
 | 
			
		||||
    <Compile Include="addons\OtpKeyProv\OathHotpKeyProv.cs" />
 | 
			
		||||
    <Compile Include="addons\OtpKeyProv\OtpAuxCachingFileStorage.cs" />
 | 
			
		||||
    <Compile Include="addons\OtpKeyProv\OtpInfo.cs" />
 | 
			
		||||
    <Compile Include="addons\OtpKeyProv\OtpUtil.cs" />
 | 
			
		||||
    <Compile Include="app\NoFileStorageFoundException.cs" />
 | 
			
		||||
    <Compile Include="app\OtpAuxCacheSupervisor.cs" />
 | 
			
		||||
    <Compile Include="CreateDatabaseActivity.cs" />
 | 
			
		||||
    <Compile Include="fileselect\FileChooserFileProvider.cs" />
 | 
			
		||||
    <Compile Include="fileselect\FileStorageSetupActivity.cs" />
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user