diff --git a/.gitignore b/.gitignore index 31b691ec..e3e3b265 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,28 @@ +PCtest + +*.suo +*.userprefs +*.user + +*.designer.cs + +src/java/kp2akeytransform/kp2akeytransform.zip + +src/Kp2aKeyboardBinding/bin +src/Kp2aKeyboardBinding/obj + +src/kp2akeytransform/bin +src/kp2akeytransform/obj + +src/Kp2aBusinessLogic/bin +src/Kp2aBusinessLogic/obj + +src/Kp2aUnitTests/bin +src/Kp2aUnitTests/obj + +src/monodroid-unittesting/MonoDroidUnitTesting/bin +src/monodroid-unittesting/MonoDroidUnitTesting/obj + /src/keepass2android/todos.cs /src/keepass2android/obj /src/keepass2android/bin diff --git a/src/KeePassLib2Android/Kp2aLog.cs b/src/KeePassLib2Android/Kp2aLog.cs index 9a0eb96f..f7a4d1ac 100644 --- a/src/KeePassLib2Android/Kp2aLog.cs +++ b/src/KeePassLib2Android/Kp2aLog.cs @@ -26,16 +26,29 @@ namespace keepass2android { private static bool? _logToFile; + private static object _fileLocker = new object(); + public static void Log(string message) { Android.Util.Log.Debug("KP2A", message); if (LogToFile) { - using (var streamWriter = File.AppendText(LogFilename)) + lock (_fileLocker) { - string stringToLog = DateTime.Now+":"+DateTime.Now.Millisecond+ " -- " + message; - streamWriter.WriteLine(stringToLog); + try + { + using (var streamWriter = File.AppendText(LogFilename)) + { + string stringToLog = DateTime.Now + ":" + DateTime.Now.Millisecond + " -- " + message; + streamWriter.WriteLine(stringToLog); + } + } + catch (Exception e) + { + Android.Util.Log.Debug("KP2A", "Couldn't write to log file. " + e); + } } + } } diff --git a/src/KeePassLib2Android/Serialization/KdbxFile.Read.cs b/src/KeePassLib2Android/Serialization/KdbxFile.Read.cs index a5ef3c81..1ce77040 100644 --- a/src/KeePassLib2Android/Serialization/KdbxFile.Read.cs +++ b/src/KeePassLib2Android/Serialization/KdbxFile.Read.cs @@ -162,17 +162,6 @@ namespace KeePassLib.Serialization finally { CommonCleanUpRead(sSource, hashedStream); } } - public static void CopyStream(Stream input, Stream output) - { - byte[] buffer = new byte[4096]; - int read; - while ((read = input.Read(buffer, 0, buffer.Length)) > 0) - { - output.Write(buffer, 0, read); - } - output.Seek(0, SeekOrigin.Begin); - } - private void CommonCleanUpRead(Stream sSource, HashingStreamEx hashedStream) { hashedStream.Close(); diff --git a/src/Kp2aBusinessLogic/IKp2aApp.cs b/src/Kp2aBusinessLogic/IKp2aApp.cs index 71c8cca1..40e340bd 100644 --- a/src/Kp2aBusinessLogic/IKp2aApp.cs +++ b/src/Kp2aBusinessLogic/IKp2aApp.cs @@ -1,5 +1,6 @@ using System; using Android.App; +using System.IO; using Android.Content; using Android.OS; using KeePassLib.Serialization; @@ -13,11 +14,15 @@ namespace keepass2android /// This also contains methods which are UI specific and should be replacable for testing. public interface IKp2aApp { + /// + /// Locks the currently open database, quicklocking if available (unless false is passed for allowQuickUnlock) + /// + void LockDatabase(bool allowQuickUnlock = true); /// - /// Set the flag that the database needs to be locked. + /// Loads the specified data as the currently open database, as unlocked. /// - void SetShutdown(); + void LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, string s, string keyFile, ProgressDialogStatusLogger statusLogger); /// /// Returns the current database diff --git a/src/Kp2aBusinessLogic/database/Database.cs b/src/Kp2aBusinessLogic/database/Database.cs index 93e2a542..50e78af7 100644 --- a/src/Kp2aBusinessLogic/database/Database.cs +++ b/src/Kp2aBusinessLogic/database/Database.cs @@ -72,25 +72,6 @@ namespace keepass2android set { _loaded = value; } } - public bool Open - { - get { return Loaded && (!Locked); } - } - - bool _locked; - public bool Locked - { - get - { - return _locked; - } - set - { - Kp2aLog.Log("Locked=" + _locked); - _locked = value; - } - } - public bool DidOpenFileChange() { if (Loaded == false) @@ -102,7 +83,10 @@ namespace keepass2android } - public void LoadData(IKp2aApp app, IOConnectionInfo iocInfo, String password, String keyfile, ProgressDialogStatusLogger status) + /// + /// Do not call this method directly. Call App.Kp2a.LoadDatabase instead. + /// + public void LoadData(IKp2aApp app, IOConnectionInfo iocInfo, MemoryStream databaseData, String password, String keyfile, ProgressDialogStatusLogger status) { PwDatabase pwDatabase = new PwDatabase(); @@ -120,12 +104,13 @@ namespace keepass2android throw new KeyFileException(); } } - + + IFileStorage fileStorage = _app.GetFileStorage(iocInfo); + var filename = fileStorage.GetFilenameWithoutPathAndExt(iocInfo); try { - IFileStorage fileStorage = _app.GetFileStorage(iocInfo); var fileVersion = _app.GetFileStorage(iocInfo).GetCurrentFileVersionFast(iocInfo); - pwDatabase.Open(fileStorage.OpenFileForRead(iocInfo), fileStorage.GetFilenameWithoutPathAndExt(iocInfo), iocInfo, compositeKey, status); + pwDatabase.Open(databaseData ?? fileStorage.OpenFileForRead(iocInfo), filename, iocInfo, compositeKey, status); LastFileVersion = fileVersion; } catch (Exception) @@ -135,8 +120,13 @@ namespace keepass2android //if we don't get a password, we don't know whether this means "empty password" or "no password" //retry without password: compositeKey.RemoveUserKey(compositeKey.GetUserKey(typeof (KcpPassword))); - pwDatabase.Open(iocInfo, compositeKey, status); - } + if (databaseData != null) + { + databaseData.Seek(0, SeekOrigin.Begin); + } + var fileVersion = _app.GetFileStorage(iocInfo).GetCurrentFileVersionFast(iocInfo); + pwDatabase.Open(databaseData ?? fileStorage.OpenFileForRead(iocInfo), filename, iocInfo, compositeKey, status); + LastFileVersion = fileVersion; } else throw; } @@ -151,15 +141,6 @@ namespace keepass2android SearchHelper = new SearchDbHelper(app); } - public bool QuickUnlockEnabled { get; set; } - - //KeyLength of QuickUnlock at time of loading the database. - //This is important to not allow an attacker to set the length to 1 when QuickUnlock is started already. - public int QuickUnlockKeyLength - { - get; - set; - } public PwGroup SearchForText(String str) { PwGroup group = SearchHelper.SearchForText(this, str); @@ -225,7 +206,6 @@ namespace keepass2android Root = null; KpDatabase = null; _loaded = false; - _locked = false; _reloadRequested = false; } diff --git a/src/Kp2aBusinessLogic/database/edit/DeleteEntry.cs b/src/Kp2aBusinessLogic/database/edit/DeleteEntry.cs index 9ba8c469..7b572d16 100644 --- a/src/Kp2aBusinessLogic/database/edit/DeleteEntry.cs +++ b/src/Kp2aBusinessLogic/database/edit/DeleteEntry.cs @@ -79,7 +79,7 @@ namespace keepass2android else { // Let's not bother recovering from a failure to save a deleted entry. It is too much work. - App.SetShutdown(); + App.LockDatabase(); } }, OnFinishToRun); } @@ -99,7 +99,7 @@ namespace keepass2android Db.Dirty.Add(pgRecycleBin); } else { // Let's not bother recovering from a failure to save a deleted entry. It is too much work. - App.SetShutdown(); + App.LockDatabase(); } }, OnFinishToRun); diff --git a/src/Kp2aBusinessLogic/database/edit/DeleteGroup.cs b/src/Kp2aBusinessLogic/database/edit/DeleteGroup.cs index 1586b0df..1c481bcd 100644 --- a/src/Kp2aBusinessLogic/database/edit/DeleteGroup.cs +++ b/src/Kp2aBusinessLogic/database/edit/DeleteGroup.cs @@ -108,7 +108,7 @@ namespace keepass2android Db.Dirty.Add(pgParent); } else { // Let's not bother recovering from a failure to save a deleted group. It is too much work. - App.SetShutdown(); + App.LockDatabase(); } }, OnFinishToRun); } @@ -146,7 +146,7 @@ namespace keepass2android } } else { // Let's not bother recovering from a failure to save a deleted group. It is too much work. - _app.SetShutdown(); + _app.LockDatabase(); } base.Run(); diff --git a/src/Kp2aBusinessLogic/database/edit/LoadDB.cs b/src/Kp2aBusinessLogic/database/edit/LoadDB.cs index 239c04a9..9e71164d 100644 --- a/src/Kp2aBusinessLogic/database/edit/LoadDB.cs +++ b/src/Kp2aBusinessLogic/database/edit/LoadDB.cs @@ -16,21 +16,25 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file */ using System; +using System.IO; +using System.Threading.Tasks; using KeePassLib.Serialization; namespace keepass2android { public class LoadDb : RunnableOnFinish { private readonly IOConnectionInfo _ioc; + private readonly Task _databaseData; private readonly String _pass; private readonly String _key; private readonly IKp2aApp _app; private readonly bool _rememberKeyfile; - public LoadDb(IKp2aApp app, IOConnectionInfo ioc, String pass, String key, OnFinish finish): base(finish) + public LoadDb(IKp2aApp app, IOConnectionInfo ioc, Task databaseData, String pass, String key, OnFinish finish): base(finish) { _app = app; _ioc = ioc; + _databaseData = databaseData; _pass = pass; _key = key; @@ -44,7 +48,7 @@ namespace keepass2android try { StatusLogger.UpdateMessage(UiStringKey.loading_database); - _app.GetDb().LoadData (_app, _ioc, _pass, _key, StatusLogger); + _app.LoadDatabase(_ioc, _databaseData == null ? null : _databaseData.Result, _pass, _key, StatusLogger); SaveFileData (_ioc, _key); } catch (KeyFileException) { diff --git a/src/keepass2android/EntryActivity.cs b/src/keepass2android/EntryActivity.cs index 48125078..a6595c7c 100644 --- a/src/keepass2android/EntryActivity.cs +++ b/src/keepass2android/EntryActivity.cs @@ -95,7 +95,7 @@ namespace keepass2android Database db = App.Kp2a.GetDb(); // Likely the app has been killed exit the activity - if (! db.Loaded) + if (!db.Loaded || (App.Kp2a.QuickLocked)) { Finish(); return; @@ -600,9 +600,7 @@ namespace keepass2android } return true; case Resource.Id.menu_lock: - App.Kp2a.SetShutdown(); - SetResult(KeePass.ExitLock); - Finish(); + App.Kp2a.LockDatabase(); return true; case Resource.Id.menu_translate: try { diff --git a/src/keepass2android/EntryEditActivity.cs b/src/keepass2android/EntryEditActivity.cs index e4859e0d..9d7cdda9 100644 --- a/src/keepass2android/EntryEditActivity.cs +++ b/src/keepass2android/EntryEditActivity.cs @@ -92,8 +92,7 @@ namespace keepass2android _closeForReload = false; // Likely the app has been killed exit the activity - Database db = App.Kp2a.GetDb(); - if (! db.Open) + if (!App.Kp2a.DatabaseIsUnlocked) { Finish(); return; @@ -107,6 +106,8 @@ namespace keepass2android } else { + Database db = App.Kp2a.GetDb(); + App.Kp2a.EntryEditActivityState = new EntryEditActivityState(); ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(this); State.ShowPassword = ! prefs.GetBoolean(GetString(Resource.String.maskpass_key), Resources.GetBoolean(Resource.Boolean.maskpass_default)); diff --git a/src/keepass2android/GroupBaseActivity.cs b/src/keepass2android/GroupBaseActivity.cs index e4c8d296..395c5a77 100644 --- a/src/keepass2android/GroupBaseActivity.cs +++ b/src/keepass2android/GroupBaseActivity.cs @@ -240,9 +240,7 @@ namespace keepass2android return true; case Resource.Id.menu_lock: - App.Kp2a.SetShutdown(); - SetResult(KeePass.ExitLock); - Finish(); + App.Kp2a.LockDatabase(); return true; case Resource.Id.menu_search: @@ -251,7 +249,7 @@ namespace keepass2android return true; case Resource.Id.menu_app_settings: - AppSettingsActivity.Launch(this); + DatabaseSettingsActivity.Launch(this); return true; case Resource.Id.menu_change_master_key: @@ -353,8 +351,7 @@ namespace keepass2android Toast.MakeText(_act, "Unrecoverable error: " + Message, ToastLength.Long).Show(); }); - App.Kp2a.SetShutdown(); - _act.Finish(); + App.Kp2a.LockDatabase(false); } } diff --git a/src/keepass2android/KeePass.cs b/src/keepass2android/KeePass.cs index a6962594..514f6bbb 100644 --- a/src/keepass2android/KeePass.cs +++ b/src/keepass2android/KeePass.cs @@ -24,6 +24,7 @@ using Android.Preferences; using Android.Content.PM; using Android.Text; using Android.Text.Method; +using KeePassLib.Serialization; namespace keepass2android { @@ -37,12 +38,8 @@ namespace keepass2android public const Result ExitLock = Result.FirstUser+1; public const Result ExitRefresh = Result.FirstUser+2; public const Result ExitRefreshTitle = Result.FirstUser+3; - public const Result ExitForceLock = Result.FirstUser+4; - public const Result ExitQuickUnlock = Result.FirstUser+5; - public const Result ExitCloseAfterTaskComplete = Result.FirstUser+6; - public const Result ExitChangeDb = Result.FirstUser+7; - public const Result ExitForceLockAndChangeDb = Result.FirstUser+8; - public const Result ExitReloadDb = Result.FirstUser+9; + public const Result ExitCloseAfterTaskComplete = Result.FirstUser+4; + public const Result ExitReloadDb = Result.FirstUser+6; AppTask _appTask; @@ -106,7 +103,7 @@ namespace keepass2android Dialog dialog = builder.Create(); dialog.DismissEvent += (sender, e) => { - StartFileSelect(); + LaunchNextActivity(); }; dialog.Show(); TextView message = (TextView) dialog.FindViewById(Android.Resource.Id.Message); @@ -119,7 +116,7 @@ namespace keepass2android } else { - StartFileSelect(); + LaunchNextActivity(); } @@ -157,12 +154,42 @@ namespace keepass2android } - private void StartFileSelect() { - Intent intent = new Intent(this, typeof(FileSelectActivity)); - //TEST Intent intent = new Intent(this, typeof(EntryActivity)); - //Intent intent = new Intent(this, typeof(SearchActivity)); - //Intent intent = new Intent(this, typeof(QuickUnlock)); + IOConnectionInfo LoadIoc(string defaultFileName) + { + return App.Kp2a.FileDbHelper.CursorToIoc(App.Kp2a.FileDbHelper.FetchFileByName(defaultFileName)); + } + private void LaunchNextActivity() { + + if (!App.Kp2a.GetDb().Loaded) + { + // Load default database + ISharedPreferences prefs = Android.Preferences.PreferenceManager.GetDefaultSharedPreferences(this); + String defaultFileName = prefs.GetString(PasswordActivity.KeyDefaultFilename, ""); + + if (defaultFileName.Length > 0) + { + try + { + PasswordActivity.Launch(this, LoadIoc(defaultFileName), _appTask); + Finish(); + return; + } + catch (Exception e) + { + Toast.MakeText(this, e.Message, ToastLength.Long); + // Ignore exception + } + } + } + else + { + PasswordActivity.Launch(this, App.Kp2a.GetDb().Ioc, _appTask); + Finish(); + return; + } + + Intent intent = new Intent(this, typeof(FileSelectActivity)); _appTask.ToIntent(intent); diff --git a/src/keepass2android/LifecycleDebugActivity.cs b/src/keepass2android/LifecycleDebugActivity.cs index 25bbc295..cba77ac9 100644 --- a/src/keepass2android/LifecycleDebugActivity.cs +++ b/src/keepass2android/LifecycleDebugActivity.cs @@ -56,8 +56,7 @@ namespace keepass2android } else { - Kp2aLog.Log(" Loaded=" + App.Kp2a.GetDb().Loaded + ", Locked=" + App.Kp2a.GetDb().Locked - + ", shutdown=" + App.Kp2a.IsShutdown()); + Kp2aLog.Log(" DatabaseIsUnlocked=" + App.Kp2a.DatabaseIsUnlocked); } } diff --git a/src/keepass2android/LockCloseActivity.cs b/src/keepass2android/LockCloseActivity.cs index 78b08b2f..89336ad7 100644 --- a/src/keepass2android/LockCloseActivity.cs +++ b/src/keepass2android/LockCloseActivity.cs @@ -15,7 +15,10 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file along with Keepass2Android. If not, see . */ +using System; +using Android.Content; using Android.OS; +using Android.App; using KeePassLib.Serialization; namespace keepass2android @@ -26,12 +29,25 @@ namespace keepass2android /// Checks in OnResume whether the timeout occured and the database must be locked/closed. public class LockCloseActivity : LockingActivity { - IOConnectionInfo _ioc; + private IOConnectionInfo _ioc; + private BroadcastReceiver _intentReceiver; protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); _ioc = App.Kp2a.GetDb().Ioc; + + _intentReceiver = new LockCloseActivityBroadcastReceiver(this); + IntentFilter filter = new IntentFilter(); + filter.AddAction(Intents.DatabaseLocked); + RegisterReceiver(_intentReceiver, filter); + } + + protected override void OnDestroy() + { + UnregisterReceiver(_intentReceiver); + + base.OnDestroy(); } @@ -49,9 +65,32 @@ namespace keepass2android App.Kp2a.CheckForOpenFileChanged(this); } + private void OnLockDatabase() + { + Kp2aLog.Log("Finishing " + ComponentName.ClassName + " due to database lock"); + SetResult(KeePass.ExitLock); + Finish(); + } - + private class LockCloseActivityBroadcastReceiver : BroadcastReceiver + { + readonly LockCloseActivity _service; + public LockCloseActivityBroadcastReceiver(LockCloseActivity service) + { + _service = service; + } + + public override void OnReceive(Context context, Intent intent) + { + switch (intent.Action) + { + case Intents.DatabaseLocked: + _service.OnLockDatabase(); + break; + } + } + } } } diff --git a/src/keepass2android/LockCloseListActivity.cs b/src/keepass2android/LockCloseListActivity.cs index 3a0117e0..dae9f657 100644 --- a/src/keepass2android/LockCloseListActivity.cs +++ b/src/keepass2android/LockCloseListActivity.cs @@ -16,6 +16,7 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file */ using System; +using Android.Content; using Android.OS; using Android.Runtime; using KeePassLib.Serialization; @@ -33,11 +34,18 @@ namespace keepass2android } IOConnectionInfo _ioc; + private BroadcastReceiver _intentReceiver; protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); _ioc = App.Kp2a.GetDb().Ioc; + + _intentReceiver = new LockCloseListActivityBroadcastReceiver(this); + IntentFilter filter = new IntentFilter(); + filter.AddAction(Intents.DatabaseLocked); + RegisterReceiver(_intentReceiver, filter); + } public LockCloseListActivity (IntPtr javaReference, JniHandleOwnership transfer) @@ -57,6 +65,39 @@ namespace keepass2android App.Kp2a.CheckForOpenFileChanged(this); } + protected override void OnDestroy() + { + UnregisterReceiver(_intentReceiver); + + base.OnDestroy(); + } + + private void OnLockDatabase() + { + Kp2aLog.Log("Finishing " + ComponentName.ClassName + " due to database lock"); + + SetResult(KeePass.ExitLock); + Finish(); + } + + private class LockCloseListActivityBroadcastReceiver : BroadcastReceiver + { + readonly LockCloseListActivity _service; + public LockCloseListActivityBroadcastReceiver(LockCloseListActivity service) + { + _service = service; + } + + public override void OnReceive(Context context, Intent intent) + { + switch (intent.Action) + { + case Intents.DatabaseLocked: + _service.OnLockDatabase(); + break; + } + } + } } } diff --git a/src/keepass2android/LockingClosePreferenceActivity.cs b/src/keepass2android/LockingClosePreferenceActivity.cs index d0a688e3..c6f152ee 100644 --- a/src/keepass2android/LockingClosePreferenceActivity.cs +++ b/src/keepass2android/LockingClosePreferenceActivity.cs @@ -15,6 +15,7 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file along with Keepass2Android. If not, see . */ +using Android.Content; using Android.OS; using KeePassLib.Serialization; @@ -25,11 +26,18 @@ namespace keepass2android IOConnectionInfo _ioc; + private BroadcastReceiver _intentReceiver; protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); _ioc = App.Kp2a.GetDb().Ioc; + + + _intentReceiver = new LockingClosePreferenceActivityBroadcastReceiver(this); + IntentFilter filter = new IntentFilter(); + filter.AddAction(Intents.DatabaseLocked); + RegisterReceiver(_intentReceiver, filter); } protected override void OnResume() { @@ -37,6 +45,41 @@ namespace keepass2android TimeoutHelper.CheckShutdown(this, _ioc); } + + + protected override void OnDestroy() + { + UnregisterReceiver(_intentReceiver); + + base.OnDestroy(); + } + + private void OnLockDatabase() + { + Kp2aLog.Log("Finishing " + ComponentName.ClassName + " due to database lock"); + + SetResult(KeePass.ExitLock); + Finish(); + } + + private class LockingClosePreferenceActivityBroadcastReceiver : BroadcastReceiver + { + readonly LockingClosePreferenceActivity _service; + public LockingClosePreferenceActivityBroadcastReceiver(LockingClosePreferenceActivity service) + { + _service = service; + } + + public override void OnReceive(Context context, Intent intent) + { + switch (intent.Action) + { + case Intents.DatabaseLocked: + _service.OnLockDatabase(); + break; + } + } + } } } diff --git a/src/keepass2android/PasswordActivity.cs b/src/keepass2android/PasswordActivity.cs index 21738fbb..3e4e7dc1 100644 --- a/src/keepass2android/PasswordActivity.cs +++ b/src/keepass2android/PasswordActivity.cs @@ -16,6 +16,7 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file */ using System; +using System.Threading.Tasks; using Android.App; using Android.Content; using Android.OS; @@ -29,6 +30,9 @@ using Android.Text; using Android.Content.PM; using KeePassLib.Keys; using KeePassLib.Serialization; +using KeePassLib.Utility; + +using MemoryStream = System.IO.MemoryStream; namespace keepass2android { @@ -48,12 +52,15 @@ namespace keepass2android private const String KeyServercredmode = "serverCredRememberMode"; private const String ViewIntent = "android.intent.action.VIEW"; - + + private Task _loadDbTask; private IOConnectionInfo _ioConnection; private String _keyFile; private bool _rememberKeyfile; ISharedPreferences _prefs; + private bool _started; + public PasswordActivity (IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) { @@ -123,114 +130,28 @@ namespace keepass2android public void LaunchNextActivity() { AppTask.AfterUnlockDatabase(this); - } - void UnloadDatabase() - { - App.Kp2a.GetDb().Clear(); - StopService(new Intent(this, typeof(QuickUnlockForegroundService))); - } - - void LockDatabase() - { - SetResult(KeePass.ExitLock); - SetEditText(Resource.Id.password, ""); - if (App.Kp2a.GetDb().QuickUnlockEnabled) - App.Kp2a.GetDb().Locked = true; - else - { - UnloadDatabase(); - } - } - - void LockAndClose() - { - LockDatabase(); - Finish(); - - } - - bool TryStartQuickUnlock() - { - if (!App.Kp2a.GetDb().QuickUnlockEnabled) - return false; - - if (App.Kp2a.GetDb().KpDatabase.MasterKey.ContainsType(typeof(KcpPassword)) == false) - return false; - KcpPassword kcpPassword = (KcpPassword)App.Kp2a.GetDb().KpDatabase.MasterKey.GetUserKey(typeof(KcpPassword)); - String password = kcpPassword.Password.ReadString(); - - if (password.Length == 0) - return false; - - App.Kp2a.GetDb().Locked = true; - - Intent i = new Intent(this, typeof(QuickUnlock)); - PutIoConnectionToIntent(_ioConnection, i); - Kp2aLog.Log("Starting QuickUnlock"); - StartActivityForResult(i,0); - return true; - } - - public void StartQuickUnlockForegroundService() - { - if (App.Kp2a.GetDb().QuickUnlockEnabled) - { - StartService(new Intent(this, typeof(QuickUnlockForegroundService))); - } - } - - bool _startedWithActivityResult; - protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) { base.OnActivityResult(requestCode, resultCode, data); - _startedWithActivityResult = true; Kp2aLog.Log("PasswordActivity.OnActivityResult "+resultCode+"/"+requestCode); - if (resultCode != KeePass.ExitCloseAfterTaskComplete) - { - //Stop service when app activity is left - StopService(new Intent(this, typeof(CopyToClipboardService))); - } - //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: - if (!TryStartQuickUnlock()) - { - SetEditText(Resource.Id.password, ""); - } + + case KeePass.ExitNormal: // Returned to this screen using the Back key, treat as locking the database + App.Kp2a.LockDatabase(); break; - case KeePass.ExitLock: - if (!TryStartQuickUnlock()) - { - LockAndClose(); - } - break; - case KeePass.ExitForceLock: - SetEditText(Resource.Id.password, ""); - UnloadDatabase(); - break; - case KeePass.ExitForceLockAndChangeDb: - UnloadDatabase(); - Finish(); - break; - case KeePass.ExitChangeDb: - LockAndClose(); + // The database has already been locked, and the quick unlock screen will be shown if appropriate break; case KeePass.ExitCloseAfterTaskComplete: + // Do not lock the database SetResult(KeePass.ExitCloseAfterTaskComplete); Finish(); break; - case KeePass.ExitQuickUnlock: - App.Kp2a.GetDb().Locked = false; - LaunchNextActivity(); - break; case KeePass.ExitReloadDb: //if the activity was killed, fill password/keyfile so the user can directly hit load again if (App.Kp2a.GetDb().Loaded) @@ -252,9 +173,9 @@ namespace keepass2android SetEditText(Resource.Id.pass_keyfile, kcpKeyfile.Path); } } - UnloadDatabase(); + App.Kp2a.LockDatabase(false); break; - case Result.Ok: + case Result.Ok: // Key file browse dialog OK'ed. if (requestCode == Intents.RequestCodeFileBrowseForKeyfile) { string filename = Util.IntentToFilename(data); if (filename != null) { @@ -331,6 +252,13 @@ namespace keepass2android } } + 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); + } + AppTask = AppTask.GetTaskInOnCreate(savedInstanceState, Intent); SetContentView(Resource.Layout.password); @@ -343,8 +271,7 @@ namespace keepass2android Window.SetSoftInputMode(SoftInput.StateVisible); Button confirmButton = (Button)FindViewById(Resource.Id.pass_ok); - confirmButton.Click += (sender, e) => - { + confirmButton.Click += (sender, e) => { String pass = GetEditText(Resource.Id.password); String key = GetEditText(Resource.Id.pass_keyfile); if (pass.Length == 0 && key.Length == 0) @@ -352,21 +279,17 @@ namespace keepass2android ErrorMessage(Resource.String.error_nopass); return; } - - // Clear before we load - UnloadDatabase(); - - // Clear the shutdown flag - App.Kp2a.ClearShutdown(); CheckBox cbQuickUnlock = (CheckBox)FindViewById(Resource.Id.enable_quickunlock); - App.Kp2a.GetDb().QuickUnlockEnabled = cbQuickUnlock.Checked; - App.Kp2a.GetDb().QuickUnlockKeyLength = int.Parse(_prefs.GetString(GetString(Resource.String.QuickUnlockLength_key), GetString(Resource.String.QuickUnlockLength_default))); - + App.Kp2a.SetQuickUnlockEnabled(cbQuickUnlock.Checked); + Handler handler = new Handler(); - LoadDb task = new LoadDb(App.Kp2a, _ioConnection, pass, key, new AfterLoad(handler, this)); - ProgressTask pt = new ProgressTask(App.Kp2a, this, task); - pt.Run(); + LoadDb task = new LoadDb(App.Kp2a, _ioConnection, _loadDbTask, pass, key, new AfterLoad(handler, this)); + _loadDbTask = null; // prevent accidental re-use + + SetNewDefaultFile(); + + new ProgressTask(App.Kp2a, this, task).Run(); }; /*CheckBox checkBox = (CheckBox) FindViewById(Resource.Id.show_password); @@ -394,31 +317,7 @@ namespace keepass2android } }; - CheckBox defaultCheck = (CheckBox)FindViewById(Resource.Id.default_database); - //Don't allow the current file to be the default if we don't have stored credentials - if ((_ioConnection.IsLocalFile() == false) && (_ioConnection.CredSaveMode != IOCredSaveMode.SaveCred)) - { - defaultCheck.Enabled = false; - } else - { - defaultCheck.Enabled = true; - } - defaultCheck.CheckedChange += (sender, e) => - { - String newDefaultFileName; - - if (e.IsChecked) - { - newDefaultFileName = _ioConnection.Path; - } else - { - newDefaultFileName = ""; - } - - ISharedPreferencesEditor editor = _prefs.Edit(); - editor.PutString(KeyDefaultFilename, newDefaultFileName); - EditorCompat.Apply(editor); - }; + ImageButton browse = (ImageButton)FindViewById(Resource.Id.browse_button); browse.Click += (sender, evt) => @@ -438,8 +337,66 @@ namespace keepass2android }; RetrieveSettings(); + } + private void SetNewDefaultFile() + { +//Don't allow the current file to be the default if we don't have stored credentials + bool makeFileDefault; + if ((_ioConnection.IsLocalFile() == false) && (_ioConnection.CredSaveMode != IOCredSaveMode.SaveCred)) + { + makeFileDefault = false; + } + else + { + makeFileDefault = true; + } + String newDefaultFileName; + if (makeFileDefault) + { + newDefaultFileName = _ioConnection.Path; + } + else + { + newDefaultFileName = ""; + } + + ISharedPreferencesEditor editor = _prefs.Edit(); + editor.PutString(KeyDefaultFilename, newDefaultFileName); + EditorCompat.Apply(editor); + } + + protected override void OnStart() + { + base.OnStart(); + _started = true; + } + + private MemoryStream LoadDbFile() + { + Kp2aLog.Log("Pre-loading database file starting"); + var fileStorage = App.Kp2a.GetFileStorage(_ioConnection); + var stream = fileStorage.OpenFileForRead(_ioConnection); + + var memoryStream = stream as MemoryStream; + if (memoryStream == null) + { + // Read the file into memory + int capacity = 4096; // Default initial capacity, if stream can't report it. + if (stream.CanSeek) + { + capacity = (int)stream.Length; + } + memoryStream = new MemoryStream(capacity); + stream.CopyTo(memoryStream); + stream.Close(); + memoryStream.Seek(0, System.IO.SeekOrigin.Begin); + } + + Kp2aLog.Log("Pre-loading database file completed"); + + return memoryStream; } protected override void OnSaveInstanceState(Bundle outState) @@ -448,41 +405,35 @@ namespace keepass2android AppTask.ToBundle(outState); } - protected override void OnResume() { + protected override void OnResume() + { base.OnResume(); - - // If the application was shutdown make sure to clear the password field, if it - // was saved in the instance state - if (App.Kp2a.IsShutdown()) { - LockDatabase(); - } - // Clear the shutdown flag - App.Kp2a.ClearShutdown(); - - if (_startedWithActivityResult) - return; - - if (App.Kp2a.GetDb().Loaded && (App.Kp2a.GetDb().Ioc != null) - && (_ioConnection != null) && (App.Kp2a.GetDb().Ioc.GetDisplayName() == _ioConnection.GetDisplayName())) + // OnResume is run every time the activity comes to the foreground. This code should only run when the activity is started (OnStart), but must + // be run in OnResume rather than OnStart so that it always occurrs after OnActivityResult (when re-creating a killed activity, OnStart occurs before OnActivityResult) + if (_started && !IsFinishing) //use !IsFinishing to make sure we're not starting another activity when we're already finishing (e.g. due to TaskComplete in OnActivityResult) { - if (App.Kp2a.GetDb().Locked == false) + _started = false; + if (App.Kp2a.DatabaseIsUnlocked) { LaunchNextActivity(); } - else + else if (App.Kp2a.QuickUnlockEnabled && App.Kp2a.QuickLocked) { - TryStartQuickUnlock(); + var i = new Intent(this, typeof(QuickUnlock)); + PutIoConnectionToIntent(_ioConnection, i); + Kp2aLog.Log("Starting QuickUnlock"); + StartActivityForResult(i, 0); + } + else if (_loadDbTask == null && _prefs.GetBoolean(GetString(Resource.String.PreloadDatabaseEnabled_key), true)) + { + // Create task to kick off file loading while the user enters the password + _loadDbTask = Task.Factory.StartNew(LoadDbFile); } } } private void RetrieveSettings() { - String defaultFilename = _prefs.GetString(KeyDefaultFilename, ""); - if (!String.IsNullOrEmpty(_ioConnection.Path) && _ioConnection.Path.Equals(defaultFilename)) { - CheckBox checkbox = (CheckBox) FindViewById(Resource.Id.default_database); - checkbox.Checked = true; - } CheckBox cbQuickUnlock = (CheckBox)FindViewById(Resource.Id.enable_quickunlock); cbQuickUnlock.Checked = _prefs.GetBoolean(GetString(Resource.String.QuickUnlockDefaultEnabled_key), true); } @@ -540,31 +491,50 @@ namespace keepass2android public override bool OnOptionsItemSelected(IMenuItem item) { switch ( item.ItemId ) { - case Resource.Id.menu_about: - AboutDialog dialog = new AboutDialog(this); - dialog.Show(); - return true; + case Resource.Id.menu_about: + AboutDialog dialog = new AboutDialog(this); + dialog.Show(); + return true; - case Resource.Id.menu_app_settings: - AppSettingsActivity.Launch(this); - return true; + case Resource.Id.menu_app_settings: + AppSettingsActivity.Launch(this); + return true; + + case Resource.Id.menu_change_db: + Intent intent = new Intent(this, typeof(FileSelectActivity)); + AppTask.ToIntent(intent); + StartActivityForResult(intent, 0); + Finish(); + return true; + + } + + return base.OnOptionsItemSelected(item); } private class AfterLoad : OnFinish { readonly PasswordActivity _act; - public AfterLoad(Handler handler, PasswordActivity act):base(handler) { + + public AfterLoad(Handler handler, PasswordActivity act):base(handler) + { _act = act; } - + public override void Run() { - if ( Success ) { - _act.StartQuickUnlockForegroundService(); + if ( Success ) + { + _act.SetEditText(Resource.Id.password, ""); + _act.LaunchNextActivity(); - } else { + + 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 + } + else + { DisplayMessage(_act); } } diff --git a/src/keepass2android/QuickUnlock.cs b/src/keepass2android/QuickUnlock.cs index 72070451..df26a061 100644 --- a/src/keepass2android/QuickUnlock.cs +++ b/src/keepass2android/QuickUnlock.cs @@ -72,18 +72,13 @@ namespace keepass2android TextView txtLabel = (TextView)FindViewById(Resource.Id.QuickUnlock_label); - int quickUnlockLength = App.Kp2a.GetDb().QuickUnlockKeyLength; + int quickUnlockLength = App.Kp2a.QuickUnlockKeyLength; txtLabel.Text = GetString(Resource.String.QuickUnlock_label, new Java.Lang.Object[]{quickUnlockLength}); EditText pwd= (EditText)FindViewById(Resource.Id.QuickUnlock_password); pwd.SetEms(quickUnlockLength); - pwd.PostDelayed(() => { - InputMethodManager keyboard = (InputMethodManager)GetSystemService(Context.InputMethodService); - keyboard.ShowSoftInput(pwd, 0); - }, 50); - SetResult(KeePass.ExitChangeDb); Button btnUnlock = (Button)FindViewById(Resource.Id.QuickUnlock_button); btnUnlock.Click += (object sender, EventArgs e) => @@ -93,11 +88,11 @@ namespace keepass2android String expectedPasswordPart = password.Substring(Math.Max(0,password.Length-quickUnlockLength),Math.Min(password.Length, quickUnlockLength)); if (pwd.Text == expectedPasswordPart) { - SetResult(KeePass.ExitQuickUnlock); + App.Kp2a.UnlockDatabase(); } else { - SetResult(KeePass.ExitForceLock); + App.Kp2a.LockDatabase(false); Toast.MakeText(this, GetString(Resource.String.QuickUnlock_fail), ToastLength.Long).Show(); } Finish(); @@ -106,21 +101,21 @@ namespace keepass2android Button btnLock = (Button)FindViewById(Resource.Id.QuickUnlock_buttonLock); btnLock.Click += (object sender, EventArgs e) => { - SetResult(KeePass.ExitForceLockAndChangeDb); + App.Kp2a.LockDatabase(false); Finish(); }; } - protected override void OnResume() { base.OnResume(); - if ( ! App.Kp2a.GetDb().Loaded ) { - SetResult(KeePass.ExitChangeDb); - Finish(); - return; - } + EditText pwd = (EditText)FindViewById(Resource.Id.QuickUnlock_password); + pwd.PostDelayed(() => + { + InputMethodManager keyboard = (InputMethodManager)GetSystemService(Context.InputMethodService); + keyboard.ShowSoftInput(pwd, 0); + }, 50); } } } diff --git a/src/keepass2android/Resources/drawable-hdpi/ic_launcher_red.png b/src/keepass2android/Resources/drawable-hdpi/ic_launcher_red.png new file mode 100644 index 00000000..797eb52f Binary files /dev/null and b/src/keepass2android/Resources/drawable-hdpi/ic_launcher_red.png differ diff --git a/src/keepass2android/Resources/drawable-mdpi/ic_launcher_red.png b/src/keepass2android/Resources/drawable-mdpi/ic_launcher_red.png new file mode 100644 index 00000000..d01a118a Binary files /dev/null and b/src/keepass2android/Resources/drawable-mdpi/ic_launcher_red.png differ diff --git a/src/keepass2android/Resources/drawable-v11/ic_unlocked_gray.png b/src/keepass2android/Resources/drawable-v11/ic_unlocked_gray.png new file mode 100644 index 00000000..c054e5e3 Binary files /dev/null and b/src/keepass2android/Resources/drawable-v11/ic_unlocked_gray.png differ diff --git a/src/keepass2android/Resources/drawable-xhdpi/ic_launcher_red.png b/src/keepass2android/Resources/drawable-xhdpi/ic_launcher_red.png new file mode 100644 index 00000000..a048b8c6 Binary files /dev/null and b/src/keepass2android/Resources/drawable-xhdpi/ic_launcher_red.png differ diff --git a/src/keepass2android/Resources/drawable-xxhdpi/ic_launcher_red.png b/src/keepass2android/Resources/drawable-xxhdpi/ic_launcher_red.png new file mode 100644 index 00000000..c96017cb Binary files /dev/null and b/src/keepass2android/Resources/drawable-xxhdpi/ic_launcher_red.png differ diff --git a/src/keepass2android/Resources/drawable/ic_launcher_red.png b/src/keepass2android/Resources/drawable/ic_launcher_red.png new file mode 100644 index 00000000..d01a118a Binary files /dev/null and b/src/keepass2android/Resources/drawable/ic_launcher_red.png differ diff --git a/src/keepass2android/Resources/drawable/ic_unlocked_gray.png b/src/keepass2android/Resources/drawable/ic_unlocked_gray.png new file mode 100644 index 00000000..3a0ca74b Binary files /dev/null and b/src/keepass2android/Resources/drawable/ic_unlocked_gray.png differ diff --git a/src/keepass2android/Resources/layout-v14/password.xml b/src/keepass2android/Resources/layout-v14/password.xml index 0c6e927f..134b6b16 100644 --- a/src/keepass2android/Resources/layout-v14/password.xml +++ b/src/keepass2android/Resources/layout-v14/password.xml @@ -91,16 +91,10 @@ android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_below="@id/keyfileLine" /> - \ No newline at end of file diff --git a/src/keepass2android/Resources/layout/password.xml b/src/keepass2android/Resources/layout/password.xml index e586320f..5fd886d4 100644 --- a/src/keepass2android/Resources/layout/password.xml +++ b/src/keepass2android/Resources/layout/password.xml @@ -79,16 +79,10 @@ android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_below="@id/pass_keyfile" /> - \ No newline at end of file diff --git a/src/keepass2android/Resources/menu-v11/password.xml b/src/keepass2android/Resources/menu-v11/password.xml index f1e24664..3d2e0470 100644 --- a/src/keepass2android/Resources/menu-v11/password.xml +++ b/src/keepass2android/Resources/menu-v11/password.xml @@ -20,6 +20,11 @@ android:title="@string/menu_about" android:icon="@android:drawable/ic_menu_help" android:showAsAction="ifRoom" + /> + + omitbackup list_size sort_key - timeout_key TanExpiresOnUse_key ShowUsernameInList_key RememberRecentFiles_key @@ -92,4 +91,11 @@ 20 28 + + ShowUnlockedNotification + true + + PreloadDatabaseEnabled + true + \ No newline at end of file diff --git a/src/keepass2android/Resources/values/strings.xml b/src/keepass2android/Resources/values/strings.xml index bfaea71f..db1ea018 100644 --- a/src/keepass2android/Resources/values/strings.xml +++ b/src/keepass2android/Resources/values/strings.xml @@ -131,6 +131,7 @@ Search Advanced Search Go to URL + Change database… Minus Never Yes @@ -218,7 +219,8 @@ Add file attachment... Add additional string Delete additional string - Database loaded, QuickUnlock enabled. + %1$s: Locked. QuickUnlock enabled. + %1$s: Unlocked. Enter server credentials File transactions Use file transactions for writing databases @@ -231,8 +233,14 @@ Make full entry accessible through the KP2A keyboard (recommended). Keyboard selection dialog Open keyboard selection dialog when entry is available through KP2A keyboard after search. - - Do you want to overwrite the existing binary with the same name? + + Notification while unlocked + Show an ongoing notification while the database is unlocked. + + Pre-load database file + Start background loading or downloading of the database file during password entry. + + Do you want to overwrite the existing binary with the same name? Overwrite existing binary? Overwrite Rename diff --git a/src/keepass2android/Resources/xml/preferences.xml b/src/keepass2android/Resources/xml/preferences.xml index 9fa794ce..b2424ad5 100644 --- a/src/keepass2android/Resources/xml/preferences.xml +++ b/src/keepass2android/Resources/xml/preferences.xml @@ -71,6 +71,13 @@ android:entryValues="@array/clipboard_timeout_values" android:dialogTitle="@string/app_timeout" android:defaultValue="@string/clipboard_timeout_default"/> + - + diff --git a/src/keepass2android/ShareUrlResults.cs b/src/keepass2android/ShareUrlResults.cs index 1628bf15..a95b09d6 100644 --- a/src/keepass2android/ShareUrlResults.cs +++ b/src/keepass2android/ShareUrlResults.cs @@ -71,7 +71,7 @@ namespace keepass2android Finish(); } - else if (_db.Locked) + else if (App.Kp2a.QuickLocked) { PasswordActivity.Launch(this,_db.Ioc, AppTask); Finish(); diff --git a/src/keepass2android/app/App.cs b/src/keepass2android/app/App.cs index ff4931ac..3038dfe6 100644 --- a/src/keepass2android/app/App.cs +++ b/src/keepass2android/app/App.cs @@ -16,11 +16,14 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file */ using System; +using System.Collections.Generic; +using System.IO; using Android.App; using Android.Content; using Android.OS; using Android.Runtime; using Android.Widget; +using KeePassLib.Keys; using KeePassLib.Serialization; using Android.Preferences; using keepass2android.Io; @@ -60,26 +63,97 @@ namespace keepass2android /// public class Kp2aApp: IKp2aApp, ICacheSupervisor { - public bool IsShutdown() - { - return _shutdown; + public void LockDatabase(bool allowQuickUnlock = true) + { + if (GetDb().Loaded) + { + if (QuickUnlockEnabled && allowQuickUnlock && + _db.KpDatabase.MasterKey.ContainsType(typeof(KcpPassword)) && + !((KcpPassword)App.Kp2a.GetDb().KpDatabase.MasterKey.GetUserKey(typeof(KcpPassword))).Password.IsEmpty) + { + if (!QuickLocked) + { + Kp2aLog.Log("QuickLocking database"); + + QuickLocked = true; + } + else + { + Kp2aLog.Log("Database already QuickLocked"); + } + } + else + { + Kp2aLog.Log("Locking database"); + + // Couldn't quick-lock, so unload database instead + _db.Clear(); + QuickLocked = false; + } + } + else + { + Kp2aLog.Log("Database not loaded, couldn't lock"); + } + + UpdateOngoingNotification(); + Application.Context.SendBroadcast(new Intent(Intents.DatabaseLocked)); } - public void SetShutdown() - { - Kp2aLog.Log("set shutdown"); - _shutdown = true; - } + public void LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, string password, string keyFile, ProgressDialogStatusLogger statusLogger) + { + _db.LoadData(this, ioConnectionInfo, memoryStream, password, keyFile, statusLogger); - public void ClearShutdown() - { - Kp2aLog.Log("clear shutdown"); - _shutdown = false; - } + UpdateOngoingNotification(); + } - private Database _db; - private bool _shutdown; + internal void UnlockDatabase() + { + QuickLocked = false; + UpdateOngoingNotification(); + } + + private void UpdateOngoingNotification() + { + // Start or update the notification icon service to reflect the current state + var ctx = Application.Context; + ctx.StartService(new Intent(ctx, typeof(OngoingNotificationsService))); + } + + public bool DatabaseIsUnlocked + { + get { return _db.Loaded && !QuickLocked; } + } + + #region QuickUnlock + public void SetQuickUnlockEnabled(bool enabled) + { + if (enabled) + { + //Set KeyLength of QuickUnlock at time of enabling. + //This is important to not allow an attacker to set the length to 1 when QuickUnlock is started already. + + var ctx = Application.Context; + var prefs = PreferenceManager.GetDefaultSharedPreferences(ctx); + QuickUnlockKeyLength = Math.Max(1, int.Parse(prefs.GetString(ctx.GetString(Resource.String.QuickUnlockLength_key), ctx.GetString(Resource.String.QuickUnlockLength_default)))); + } + QuickUnlockEnabled = enabled; + } + + public bool QuickUnlockEnabled { get; private set; } + + public int QuickUnlockKeyLength { get; private set; } + + /// + /// If true, the database must be regarded as locked and not exposed to the user. + /// + public bool QuickLocked { get; private set; } + + #endregion + + private Database _db; + /// /// See comments to EntryEditActivityState. /// @@ -124,6 +198,7 @@ namespace keepass2android { if (_db.ReloadRequested) { + LockDatabase(false); activity.SetResult(KeePass.ExitReloadDb); activity.Finish(); //todo: return? @@ -143,6 +218,7 @@ namespace keepass2android (dlgSender, dlgEvt) => { _db.ReloadRequested = true; + LockDatabase(false); activity.SetResult(KeePass.ExitReloadDb); activity.Finish(); @@ -223,7 +299,7 @@ namespace keepass2android public RealProgressDialog(Context ctx) { - this._pd = new ProgressDialog(ctx); + _pd = new ProgressDialog(ctx); } public void SetTitle(string title) diff --git a/src/keepass2android/app/ApplicationBroadcastReceiver.cs b/src/keepass2android/app/ApplicationBroadcastReceiver.cs new file mode 100644 index 00000000..93a6f8c1 --- /dev/null +++ b/src/keepass2android/app/ApplicationBroadcastReceiver.cs @@ -0,0 +1,25 @@ +using System; +using System.Linq; +using Android.Content; +using Android.App; + + +namespace keepass2android +{ + [BroadcastReceiver] + [IntentFilter(new[] { Intents.LockDatabase })] + public class ApplicationBroadcastReceiver : BroadcastReceiver + { + public override void OnReceive(Context context, Intent intent) + { + Kp2aLog.Log("Received broadcast intent: " + intent.Action); + + switch (intent.Action) + { + case Intents.LockDatabase: + App.Kp2a.LockDatabase(); + break; + } + } + } +} \ No newline at end of file diff --git a/src/keepass2android/fileselect/FileSelectActivity.cs b/src/keepass2android/fileselect/FileSelectActivity.cs index cc374636..ec9d7d7c 100644 --- a/src/keepass2android/fileselect/FileSelectActivity.cs +++ b/src/keepass2android/fileselect/FileSelectActivity.cs @@ -63,11 +63,6 @@ namespace keepass2android internal AppTask AppTask; - IOConnectionInfo LoadIoc(string defaultFileName) - { - return _DbHelper.CursorToIoc(_DbHelper.FetchFileByName(defaultFileName)); - } - void ShowFilenameDialog(bool showOpenButton, bool showCreateButton, bool showBrowseButton, string defaultFilename, string detailsText, int requestCodeBrowse) { AlertDialog.Builder builder = new AlertDialog.Builder(this); @@ -319,14 +314,20 @@ namespace keepass2android public override void Run() { if (Success) { + // Update the ongoing notification + _activity.StartService(new Intent(_activity, typeof(OngoingNotificationsService))); + + if (_activity.RememberRecentFiles()) { // Add to recent files FileDbHelper dbHelper = App.Kp2a.FileDbHelper; + //TODO: getFilename always returns "" -> bug? dbHelper.CreateFile(_ioc, Filename); } + GroupActivity.Launch(_activity, _activity.AppTask); } else { @@ -391,6 +392,7 @@ namespace keepass2android ioc.Password = password; ioc.CredSaveMode = (IOCredSaveMode)credentialRememberMode; PasswordActivity.Launch(this, ioc, AppTask); + Finish(); }); builder.SetView(LayoutInflater.Inflate(Resource.Layout.url_credentials, null)); builder.SetNeutralButton(GetString(Android.Resource.String.Cancel), @@ -406,6 +408,7 @@ namespace keepass2android try { PasswordActivity.Launch(this, ioc, AppTask); + Finish(); } catch (Java.IO.FileNotFoundException) { Toast.MakeText(this, Resource.String.FileNotFound, ToastLength.Long).Show(); @@ -489,36 +492,6 @@ namespace keepass2android _fileSelectButtons.UpdateExternalStorageWarning(); - if (!_createdWithActivityResult) - { - if ((Intent.Action == Intent.ActionSend) && (App.Kp2a.GetDb().Loaded)) - { - PasswordActivity.Launch(this, App.Kp2a.GetDb().Ioc , AppTask); - } else - { - - // Load default database - ISharedPreferences prefs = Android.Preferences.PreferenceManager.GetDefaultSharedPreferences(this); - String defaultFileName = prefs.GetString(PasswordActivity.KeyDefaultFilename, ""); - - if (defaultFileName.Length > 0) - { - Java.IO.File db = new Java.IO.File(defaultFileName); - - if (db.Exists()) - { - try - { - PasswordActivity.Launch(this, LoadIoc(defaultFileName), AppTask); - } catch (Exception e) - { - Toast.MakeText(this, e.Message, ToastLength.Long); - // Ignore exception - } - } - } - } - } } @@ -526,6 +499,15 @@ namespace keepass2android { base.OnStart(); Kp2aLog.Log("FileSelect.OnStart"); + + var db = App.Kp2a.GetDb(); + if (db.Loaded) + { + PasswordActivity.Launch(this, db.Ioc, AppTask); + Finish(); + } + + } public override bool OnCreateOptionsMenu(IMenu menu) { base.OnCreateOptionsMenu(menu); diff --git a/src/keepass2android/intents/Intents.cs b/src/keepass2android/intents/Intents.cs index 59762065..fae3a268 100644 --- a/src/keepass2android/intents/Intents.cs +++ b/src/keepass2android/intents/Intents.cs @@ -22,9 +22,14 @@ namespace keepass2android /// /// Contains constants to be used in intents /// - public class Intents { - public const String Timeout = "keepass2android.timeout"; + public class Intents + { + /// Broadcast this intent to lock the database + public const String LockDatabase = "keepass2android.lock_database"; + /// This intent will be broadcast once the database has been locked. Sensitive information displayed should be hidden and unloaded. + public const String DatabaseLocked = "keepass2android.database_locked"; + public const String CopyUsername = "keepass2android.copy_username"; public const String CopyPassword = "keepass2android.copy_password"; public const String CheckKeyboard = "keepass2android.check_keyboard"; diff --git a/src/keepass2android/keepass2android.csproj b/src/keepass2android/keepass2android.csproj index 4e30551a..ec1cc561 100644 --- a/src/keepass2android/keepass2android.csproj +++ b/src/keepass2android/keepass2android.csproj @@ -77,6 +77,7 @@ + @@ -86,6 +87,8 @@ + + @@ -127,12 +130,10 @@ - - @@ -703,4 +704,25 @@ Designer + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/keepass2android/search/SearchProvider.cs b/src/keepass2android/search/SearchProvider.cs index d2282cb0..6b6f5e71 100644 --- a/src/keepass2android/search/SearchProvider.cs +++ b/src/keepass2android/search/SearchProvider.cs @@ -70,7 +70,7 @@ namespace keepass2android.search public override Android.Database.ICursor Query(Android.Net.Uri uri, string[] projection, string selection, string[] selectionArgs, string sortOrder) { - if (_db.Open) // Can't show suggestions if the database is locked! + if (App.Kp2a.DatabaseIsUnlocked) // Can't show suggestions if the database is locked! { switch ((UriMatches)UriMatcher.Match(uri)) { diff --git a/src/keepass2android/search/SearchResults.cs b/src/keepass2android/search/SearchResults.cs index 256bd133..bc2d7a84 100644 --- a/src/keepass2android/search/SearchResults.cs +++ b/src/keepass2android/search/SearchResults.cs @@ -56,11 +56,9 @@ namespace keepass2android.search private void ProcessIntent(Intent intent) { - _db = App.Kp2a.GetDb(); - - // Likely the app has been killed exit the activity - if ( ! _db.Open ) { + if (!App.Kp2a.DatabaseIsUnlocked) + { Finish(); } diff --git a/src/keepass2android/services/CopyToClipboardService.cs b/src/keepass2android/services/CopyToClipboardService.cs index c64e5450..36868f5d 100644 --- a/src/keepass2android/services/CopyToClipboardService.cs +++ b/src/keepass2android/services/CopyToClipboardService.cs @@ -57,7 +57,7 @@ namespace keepass2android CopyToClipboardBroadcastReceiver _copyToClipBroadcastReceiver; NotificationDeletedBroadcastReceiver _notificationDeletedBroadcastReceiver; - + StopOnLockBroadcastReceiver _stopOnLockBroadcastReceiver; public CopyToClipboardService() { @@ -73,7 +73,12 @@ namespace keepass2android public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId) { Kp2aLog.Log("Received intent to provide access to entry"); - + + _stopOnLockBroadcastReceiver = new StopOnLockBroadcastReceiver(this); + IntentFilter filter = new IntentFilter(); + filter.AddAction(Intents.DatabaseLocked); + RegisterReceiver(_stopOnLockBroadcastReceiver, filter); + String uuidBytes = intent.GetStringExtra(EntryActivity.KeyEntry); bool closeAfterCreate = intent.GetBooleanExtra(EntryActivity.KeyCloseAfterCreate, false); @@ -99,12 +104,25 @@ namespace keepass2android return StartCommandResult.RedeliverIntent; } + private void OnLockDatabase() + { + Kp2aLog.Log("Stopping clipboard service due to database lock"); + + StopSelf(); + } + private NotificationManager _notificationManager; private int _numElementsToWaitFor; public override void OnDestroy() { + Kp2aLog.Log("CopyToClipboardService.OnDestroy"); + // These members might never get initialized if the app timed out + if (_stopOnLockBroadcastReceiver != null) + { + UnregisterReceiver(_stopOnLockBroadcastReceiver); + } if (_copyToClipBroadcastReceiver != null) { UnregisterReceiver(_copyToClipBroadcastReceiver); @@ -114,7 +132,10 @@ namespace keepass2android UnregisterReceiver(_notificationDeletedBroadcastReceiver); } if ( _notificationManager != null ) { - _notificationManager.CancelAll(); + _notificationManager.Cancel(NotifyPassword); + _notificationManager.Cancel(NotifyUsername); + _notificationManager.Cancel(NotifyKeyboard); + _numElementsToWaitFor= 0; clearKeyboard(); } @@ -142,8 +163,10 @@ namespace keepass2android { // Notification Manager _notificationManager = (NotificationManager)GetSystemService(NotificationService); - - _notificationManager.CancelAll(); + + _notificationManager.Cancel(NotifyPassword); + _notificationManager.Cancel(NotifyUsername); + _notificationManager.Cancel(NotifyKeyboard); _numElementsToWaitFor = 0; clearKeyboard(); @@ -358,7 +381,24 @@ namespace keepass2android return notify; } + private class StopOnLockBroadcastReceiver : BroadcastReceiver + { + readonly CopyToClipboardService _service; + public StopOnLockBroadcastReceiver(CopyToClipboardService service) + { + _service = service; + } + public override void OnReceive(Context context, Intent intent) + { + switch (intent.Action) + { + case Intents.DatabaseLocked: + _service.OnLockDatabase(); + break; + } + } + } class CopyToClipboardBroadcastReceiver: BroadcastReceiver { diff --git a/src/keepass2android/services/OngoingNotificationsService.cs b/src/keepass2android/services/OngoingNotificationsService.cs new file mode 100644 index 00000000..d9a620af --- /dev/null +++ b/src/keepass2android/services/OngoingNotificationsService.cs @@ -0,0 +1,187 @@ +/* +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 2 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 Android.App; +using Android.Content; +using Android.Graphics; +using Android.OS; +using Android.Preferences; +using Android.Support.V4.App; +using KeePassLib.Utility; + +namespace keepass2android +{ + /// + /// Service for showing ongoing notifications + /// + /// Shows database unlocked warning persistent notification + /// Shows Quick-Unlock notification + /// + [Service] + public class OngoingNotificationsService : Service + { + + #region Service + private const int QuickUnlockId = 100; + private const int UnlockedWarningId = 200; + + public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId) + { + Kp2aLog.Log("Starting/Updating OngoingNotificationsService. Database " + (App.Kp2a.DatabaseIsUnlocked ? "Unlocked" : (App.Kp2a.QuickLocked ? "QuickLocked" : "Locked"))); + + var notificationManager = (NotificationManager)GetSystemService(NotificationService); + + // Set the icon to reflect the current state + if (App.Kp2a.DatabaseIsUnlocked) + { + // Clear current foreground status and QuickUnlock icon + StopForeground(true); + + if (ShowUnlockedNotification) + { + // No need for task to get foreground priority, we don't need any special treatment just for showing that the database is unlocked + notificationManager.Notify(UnlockedWarningId, GetUnlockedNotification()); + } + else + { + notificationManager.Cancel(UnlockedWarningId); + } + } + else + { + notificationManager.Cancel(UnlockedWarningId); + + if (App.Kp2a.QuickLocked) + { + // Show the Quick Unlock notification + StartForeground(QuickUnlockId, GetQuickUnlockNotification()); + } + else + { + // Not showing any notification, database is locked, no point in keeping running + StopSelf(); + } + } + + return StartCommandResult.NotSticky; + } + + private bool ShowUnlockedNotification + { + get { return PreferenceManager.GetDefaultSharedPreferences(this).GetBoolean(GetString(Resource.String.ShowUnlockedNotification_key), Resources.GetBoolean(Resource.Boolean.ShowUnlockedNotification_default)); } + } + + public override void OnTaskRemoved(Intent rootIntent) + { + base.OnTaskRemoved(rootIntent); + + Kp2aLog.Log("OngoingNotificationsService.OnTaskRemoved: " + rootIntent.Action); + + // If the user has closed the task (probably by swiping it out of the recent apps list) then lock the database + App.Kp2a.LockDatabase(); + } + + public override void OnDestroy() + { + base.OnDestroy(); + + var notificationManager = (NotificationManager)GetSystemService(NotificationService); + notificationManager.Cancel(UnlockedWarningId); + // Quick Unlock notification should be removed automatically by the service (if present), as it was the foreground notification. + + Kp2aLog.Log("OngoingNotificationsService.OnDestroy"); + + // If the service is killed, then lock the database immediately (as the unlocked warning icon will no longer display). + if (App.Kp2a.DatabaseIsUnlocked) + { + App.Kp2a.LockDatabase(); + } + } + + public override IBinder OnBind(Intent intent) + { + return null; + } + + #endregion + + #region QuickUnlock + + private Notification GetQuickUnlockNotification() + { + NotificationCompat.Builder builder = + new NotificationCompat.Builder(this) + .SetSmallIcon(Resource.Drawable.ic_launcher_gray) + .SetLargeIcon(BitmapFactory.DecodeResource(Resources, AppNames.LauncherIcon)) + .SetContentTitle(GetString(Resource.String.app_name)) + .SetContentText(GetString(Resource.String.database_loaded_quickunlock_enabled, GetDatabaseName())); + + var startKp2APendingIntent = GetSwitchToAppPendingIntent(); + builder.SetContentIntent(startKp2APendingIntent); + + return builder.Build(); + } + + #endregion + + #region Unlocked Warning + + private Notification GetUnlockedNotification() + { + NotificationCompat.Builder builder = + new NotificationCompat.Builder(this) + .SetOngoing(true) + .SetSmallIcon(Resource.Drawable.ic_unlocked_gray) + .SetLargeIcon(BitmapFactory.DecodeResource(Resources, Resource.Drawable.ic_launcher_red)) + .SetContentTitle(GetString(Resource.String.app_name)) + .SetContentText(GetString(Resource.String.database_loaded_unlocked, GetDatabaseName())); + + // Default action is to show Kp2A + builder.SetContentIntent(GetSwitchToAppPendingIntent()); + // Additional action to allow locking the database + builder.AddAction(Android.Resource.Drawable.IcLockLock, GetString(Resource.String.menu_lock), PendingIntent.GetBroadcast(this, 0, new Intent(Intents.LockDatabase), PendingIntentFlags.UpdateCurrent)); + + return builder.Build(); + } + + private PendingIntent GetSwitchToAppPendingIntent() + { + var startKp2aIntent = new Intent(this, typeof(KeePass)); + startKp2aIntent.SetAction(Intent.ActionMain); + startKp2aIntent.AddCategory(Intent.CategoryLauncher); + + return PendingIntent.GetActivity(this, 0, startKp2aIntent, PendingIntentFlags.UpdateCurrent); + } + + private static string GetDatabaseName() + { + + var db = App.Kp2a.GetDb().KpDatabase; + var name = db.Name; + if (String.IsNullOrEmpty(name)) + { + //todo: if paranoid ("don't remember recent files") return "***" + name = UrlUtil.StripExtension(UrlUtil.GetFileName(db.IOConnectionInfo.Path)); + } + + return name; + } + #endregion + } +} + diff --git a/src/keepass2android/services/QuickUnlockForegroundService.cs b/src/keepass2android/services/QuickUnlockForegroundService.cs deleted file mode 100644 index a3d3b813..00000000 --- a/src/keepass2android/services/QuickUnlockForegroundService.cs +++ /dev/null @@ -1,79 +0,0 @@ -/* -This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. - - 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 2 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 Android.App; -using Android.Content; -using Android.OS; -using Android.Support.V4.App; -using Android.Graphics; - -namespace keepass2android -{ - /// - /// This service is started as soon as a Database with QuickUnlock enabled is opened. - /// Its only purpose is to be a foreground service which prevents the App from being killed (in most situations) - /// -[Service] - public class QuickUnlockForegroundService : Service - { - public override IBinder OnBind(Intent intent) - { - return null; - } - - const int QuickUnlockForegroundServiceId = 238787; - - public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId) - { - Kp2aLog.Log("Starting QuickUnlockForegroundService"); - - //create notification item - NotificationCompat.Builder mBuilder = - new NotificationCompat.Builder(this) - .SetSmallIcon(Resource.Drawable.ic_launcher_gray) - .SetLargeIcon(BitmapFactory.DecodeResource(Resources, AppNames.LauncherIcon)) - .SetContentTitle(GetText(Resource.String.app_name)) - .SetContentText(GetText(Resource.String.database_loaded_quickunlock_enabled)); - - Intent startKp2aIntent = new Intent(this, typeof(KeePass)); - startKp2aIntent.SetAction(Intent.ActionMain); - startKp2aIntent.AddCategory(Intent.CategoryLauncher); - - PendingIntent startKp2APendingIntent = - PendingIntent.GetActivity(this, 0, startKp2aIntent, PendingIntentFlags.UpdateCurrent); - mBuilder.SetContentIntent(startKp2APendingIntent); - - StartForeground(QuickUnlockForegroundServiceId , mBuilder.Build()); - - return StartCommandResult.NotSticky; - - } - - public override void OnCreate() - { - base.OnCreate(); - Kp2aLog.Log("Creating QuickUnlockForegroundService"); - } - - public override void OnDestroy() - { - base.OnDestroy(); - Kp2aLog.Log("Destroying QuickUnlockForegroundService"); - } - } -} - diff --git a/src/keepass2android/services/TimeoutService.cs b/src/keepass2android/services/TimeoutService.cs deleted file mode 100644 index 3c960f92..00000000 --- a/src/keepass2android/services/TimeoutService.cs +++ /dev/null @@ -1,129 +0,0 @@ -/* -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 2 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 Android.App; -using Android.Content; -using Android.OS; -using Android.Runtime; -using Android.Util; - -namespace keepass2android -{ - /// - /// Manages timeout to lock the database after some idle time - /// - [Service] - public class TimeoutService : Service { - private const String Tag = "KeePass2Android Timer"; - private BroadcastReceiver _intentReceiver; - - - public TimeoutService (IntPtr javaReference, JniHandleOwnership transfer) - : base(javaReference, transfer) - { - _binder = new TimeoutBinder(this); - } - public TimeoutService() - { - _binder = new TimeoutBinder(this); - } - - public override void OnCreate() { - base.OnCreate(); - - _intentReceiver = new MyBroadcastReceiver(this); - - IntentFilter filter = new IntentFilter(); - filter.AddAction(Intents.Timeout); - RegisterReceiver(_intentReceiver, filter); - - } - - - public override void OnStart(Intent intent, int startId) { - base.OnStart(intent, startId); - - Log.Debug(Tag, "Timeout service started"); - } - - private void Timeout() { - Log.Debug(Tag, "Timeout"); - App.Kp2a.SetShutdown(); - - NotificationManager nm = (NotificationManager) GetSystemService(NotificationService); - nm.CancelAll(); - StopService(new Intent(this, typeof(CopyToClipboardService))); - StopSelf(); - } - - public override void OnDestroy() { - base.OnDestroy(); - - Log.Debug(Tag, "Timeout service stopped"); - - UnregisterReceiver(_intentReceiver); - } - - public class TimeoutBinder : Binder - { - readonly TimeoutService _service; - - public TimeoutBinder(TimeoutService service) - { - _service = service; - } - - public TimeoutService GetService() { - return _service; - } - } - - private readonly IBinder _binder; - - - public override IBinder OnBind(Intent intent) { - return _binder; - } - - [BroadcastReceiver] - public class MyBroadcastReceiver: BroadcastReceiver - { - public MyBroadcastReceiver() - { - //dummy constructor required for MonoForAndroid, not called. - throw new NotImplementedException(); - } - - readonly TimeoutService _timeoutService; - public MyBroadcastReceiver (TimeoutService timeoutService) - { - _timeoutService = timeoutService; - } - - public override void OnReceive(Context context, Intent intent) { - String action = intent.Action; - - if ( action.Equals(Intents.Timeout) ) { - _timeoutService.Timeout(); - } - } - } - - } -} - diff --git a/src/keepass2android/settings/AppSettingsActivity.cs b/src/keepass2android/settings/AppSettingsActivity.cs index 6f2741b7..23596856 100644 --- a/src/keepass2android/settings/AppSettingsActivity.cs +++ b/src/keepass2android/settings/AppSettingsActivity.cs @@ -16,122 +16,49 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file */ using System; -using System.Globalization; using Android.App; using Android.Content; using Android.OS; -using Android.Widget; using Android.Preferences; -using KeePassLib.Cryptography.Cipher; namespace keepass2android { /// - /// Activity to configure the app + /// Activity to configure the application, without database settings. Does not require an unlocked database, or close when the database is locked /// [Activity (Label = "@string/app_name", Theme="@style/NoTitleBar")] - public class AppSettingsActivity : LockingClosePreferenceActivity { - public static bool KeyfileDefault = false; - - public static void Launch(Context ctx) { - Intent i = new Intent(ctx, typeof(AppSettingsActivity)); - - ctx.StartActivity(i); + public class AppSettingsActivity : LockingPreferenceActivity + { + public static void Launch(Context ctx) + { + ctx.StartActivity(new Intent(ctx, typeof(AppSettingsActivity))); } - - protected override void OnCreate(Bundle savedInstanceState) { + + protected override void OnCreate(Bundle savedInstanceState) + { base.OnCreate(savedInstanceState); AddPreferencesFromResource(Resource.Xml.preferences); - Preference keyFile = FindPreference(GetString(Resource.String.keyfile_key)); - keyFile.PreferenceChange += (sender, e) => + FindPreference(GetString(Resource.String.keyfile_key)).PreferenceChange += OnRememberKeyFileHistoryChanged; + FindPreference(GetString(Resource.String.ShowUnlockedNotification_key)).PreferenceChange += OnShowUnlockedNotificationChanged;; + + FindPreference(GetString(Resource.String.db_key)).Enabled = false; + } + + internal static void OnRememberKeyFileHistoryChanged(object sender, Preference.PreferenceChangeEventArgs eventArgs) + { + if (!(bool)eventArgs.NewValue) { - bool value = (bool) e.NewValue; - - if ( ! value ) { - FileDbHelper helper = App.Kp2a.FileDbHelper; - - helper.DeleteAllKeys(); - } - }; - - Database db = App.Kp2a.GetDb(); - if ( db.Open ) { - Preference rounds = FindPreference(GetString(Resource.String.rounds_key)); - rounds.PreferenceChange += (sender, e) => - { - setRounds(App.Kp2a.GetDb(), e.Preference); - }; - - Preference defaultUser = FindPreference(GetString(Resource.String.default_username_key)); - ((EditTextPreference)defaultUser).EditText.Text = db.KpDatabase.DefaultUserName; - ((EditTextPreference)defaultUser).Text = db.KpDatabase.DefaultUserName; - defaultUser.PreferenceChange += (sender, e) => - { - DateTime previousUsernameChanged = db.KpDatabase.DefaultUserNameChanged; - String previousUsername = db.KpDatabase.DefaultUserName; - db.KpDatabase.DefaultUserName = e.NewValue.ToString(); - - SaveDb save = new SaveDb(this, App.Kp2a, new ActionOnFinish( (success, message) => - { - if (!success) - { - db.KpDatabase.DefaultUserName = previousUsername; - db.KpDatabase.DefaultUserNameChanged = previousUsernameChanged; - Toast.MakeText(this, message, ToastLength.Long).Show(); - } - })); - ProgressTask pt = new ProgressTask(App.Kp2a, this, save); - pt.Run(); - }; - - Preference databaseName = FindPreference(GetString(Resource.String.database_name_key)); - ((EditTextPreference)databaseName).EditText.Text = db.KpDatabase.Name; - ((EditTextPreference)databaseName).Text = db.KpDatabase.Name; - databaseName.PreferenceChange += (sender, e) => - { - DateTime previousNameChanged = db.KpDatabase.NameChanged; - String previousName = db.KpDatabase.Name; - db.KpDatabase.Name = e.NewValue.ToString(); - - SaveDb save = new SaveDb(this, App.Kp2a, new ActionOnFinish( (success, message) => - { - if (!success) - { - db.KpDatabase.Name = previousName; - db.KpDatabase.NameChanged = previousNameChanged; - Toast.MakeText(this, message, ToastLength.Long).Show(); - } - })); - ProgressTask pt = new ProgressTask(App.Kp2a, this, save); - pt.Run(); - }; - - - - setRounds(db, rounds); - - Preference algorithm = FindPreference(GetString(Resource.String.algorithm_key)); - setAlgorithm(db, algorithm); - - } else { - Preference dbSettings = FindPreference(GetString(Resource.String.db_key)); - dbSettings.Enabled = false; + App.Kp2a.FileDbHelper.DeleteAllKeys(); } - } - private void setRounds(Database db, Preference rounds) { - rounds.Summary = db.KpDatabase.KeyEncryptionRounds.ToString(CultureInfo.InvariantCulture); + internal static void OnShowUnlockedNotificationChanged(object sender, Preference.PreferenceChangeEventArgs eventArgs) + { + var ctx = ((Preference)sender).Context; + ctx.StartService(new Intent(ctx, typeof(OngoingNotificationsService))); } - - private void setAlgorithm(Database db, Preference algorithm) { - - algorithm.Summary = CipherPool.GlobalPool.GetCipher(db.KpDatabase.DataCipherUuid).DisplayName; - } - - } } diff --git a/src/keepass2android/settings/DatabaseSettingsActivity.cs b/src/keepass2android/settings/DatabaseSettingsActivity.cs new file mode 100644 index 00000000..b2ce6cb7 --- /dev/null +++ b/src/keepass2android/settings/DatabaseSettingsActivity.cs @@ -0,0 +1,121 @@ +/* +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 2 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.Globalization; +using Android.App; +using Android.Content; +using Android.OS; +using Android.Widget; +using Android.Preferences; +using KeePassLib.Cryptography.Cipher; + +namespace keepass2android +{ + /// + /// Activity to configure the application and database settings. The database must be unlocked, and this activity will close if it becomes locked. + /// + [Activity (Label = "@string/app_name", Theme="@style/NoTitleBar")] + public class DatabaseSettingsActivity : LockingClosePreferenceActivity + { + public static void Launch(Context ctx) + { + ctx.StartActivity(new Intent(ctx, typeof(DatabaseSettingsActivity))); + } + + protected override void OnCreate(Bundle savedInstanceState) + { + base.OnCreate(savedInstanceState); + + AddPreferencesFromResource(Resource.Xml.preferences); + + // Re-use the change handlers for the application settings + FindPreference(GetString(Resource.String.keyfile_key)).PreferenceChange += AppSettingsActivity.OnRememberKeyFileHistoryChanged; + FindPreference(GetString(Resource.String.ShowUnlockedNotification_key)).PreferenceChange += AppSettingsActivity.OnShowUnlockedNotificationChanged; + + Database db = App.Kp2a.GetDb(); + + Preference rounds = FindPreference(GetString(Resource.String.rounds_key)); + rounds.PreferenceChange += (sender, e) => SetRounds(db, e.Preference); + + Preference defaultUser = FindPreference(GetString(Resource.String.default_username_key)); + ((EditTextPreference)defaultUser).EditText.Text = db.KpDatabase.DefaultUserName; + ((EditTextPreference)defaultUser).Text = db.KpDatabase.DefaultUserName; + defaultUser.PreferenceChange += (sender, e) => + { + DateTime previousUsernameChanged = db.KpDatabase.DefaultUserNameChanged; + String previousUsername = db.KpDatabase.DefaultUserName; + db.KpDatabase.DefaultUserName = e.NewValue.ToString(); + + SaveDb save = new SaveDb(this, App.Kp2a, new ActionOnFinish( (success, message) => + { + if (!success) + { + db.KpDatabase.DefaultUserName = previousUsername; + db.KpDatabase.DefaultUserNameChanged = previousUsernameChanged; + Toast.MakeText(this, message, ToastLength.Long).Show(); + } + })); + ProgressTask pt = new ProgressTask(App.Kp2a, this, save); + pt.Run(); + }; + + Preference databaseName = FindPreference(GetString(Resource.String.database_name_key)); + ((EditTextPreference)databaseName).EditText.Text = db.KpDatabase.Name; + ((EditTextPreference)databaseName).Text = db.KpDatabase.Name; + databaseName.PreferenceChange += (sender, e) => + { + DateTime previousNameChanged = db.KpDatabase.NameChanged; + String previousName = db.KpDatabase.Name; + db.KpDatabase.Name = e.NewValue.ToString(); + + SaveDb save = new SaveDb(this, App.Kp2a, new ActionOnFinish( (success, message) => + { + if (!success) + { + db.KpDatabase.Name = previousName; + db.KpDatabase.NameChanged = previousNameChanged; + Toast.MakeText(this, message, ToastLength.Long).Show(); + } + else + { + // Name is reflected in notification, so update it + StartService(new Intent(this, typeof(OngoingNotificationsService))); + } + })); + ProgressTask pt = new ProgressTask(App.Kp2a, this, save); + pt.Run(); + }; + + SetRounds(db, rounds); + + Preference algorithm = FindPreference(GetString(Resource.String.algorithm_key)); + SetAlgorithm(db, algorithm); + } + + private void SetRounds(Database db, Preference rounds) + { + rounds.Summary = db.KpDatabase.KeyEncryptionRounds.ToString(CultureInfo.InvariantCulture); + } + + private void SetAlgorithm(Database db, Preference algorithm) + { + algorithm.Summary = CipherPool.GlobalPool.GetCipher(db.KpDatabase.DataCipherUuid).DisplayName; + } + } +} + diff --git a/src/keepass2android/settings/RoundsPreference.cs b/src/keepass2android/settings/RoundsPreference.cs index 9bd50e56..c3e59191 100644 --- a/src/keepass2android/settings/RoundsPreference.cs +++ b/src/keepass2android/settings/RoundsPreference.cs @@ -31,7 +31,6 @@ namespace keepass2android.settings /// public class RoundsPreference : DialogPreference { - internal PwDatabase PwDatabase; internal TextView RoundsView; protected override View OnCreateDialogView() { @@ -40,8 +39,7 @@ namespace keepass2android.settings RoundsView = (TextView) view.FindViewById(Resource.Id.rounds); Database db = App.Kp2a.GetDb(); - PwDatabase = db.KpDatabase; - ulong numRounds = PwDatabase.KeyEncryptionRounds; + ulong numRounds = db.KpDatabase.KeyEncryptionRounds; RoundsView.Text = numRounds.ToString(CultureInfo.InvariantCulture); return view; @@ -69,15 +67,17 @@ namespace keepass2android.settings if ( rounds < 1 ) { rounds = 1; } - - ulong oldRounds = PwDatabase.KeyEncryptionRounds; + + Database db = App.Kp2a.GetDb(); + + ulong oldRounds = db.KpDatabase.KeyEncryptionRounds; if (oldRounds == rounds) { return; } - PwDatabase.KeyEncryptionRounds = rounds; + db.KpDatabase.KeyEncryptionRounds = rounds; Handler handler = new Handler(); SaveDb save = new SaveDb(Context, App.Kp2a, new AfterSave(Context, handler, oldRounds, this)); @@ -108,7 +108,8 @@ namespace keepass2android.settings } } else { DisplayMessage(_ctx); - _pref.PwDatabase.KeyEncryptionRounds = _oldRounds; + + App.Kp2a.GetDb().KpDatabase.KeyEncryptionRounds = _oldRounds; } base.Run(); diff --git a/src/keepass2android/timeout/TimeoutHelper.cs b/src/keepass2android/timeout/TimeoutHelper.cs index 107a80bd..ad95a2d4 100644 --- a/src/keepass2android/timeout/TimeoutHelper.cs +++ b/src/keepass2android/timeout/TimeoutHelper.cs @@ -31,21 +31,15 @@ namespace keepass2android private static class Timeout { - private const int RequestId = 0; private const long DefaultTimeout = 5 * 60 * 1000; // 5 minutes private static PendingIntent BuildIntent(Context ctx) { - Intent intent = new Intent(Intents.Timeout); - PendingIntent sender = PendingIntent.GetBroadcast(ctx, RequestId, intent, PendingIntentFlags.CancelCurrent); - - return sender; + return PendingIntent.GetBroadcast(ctx, 0, new Intent(Intents.LockDatabase), PendingIntentFlags.UpdateCurrent); } public static void Start(Context ctx) { - - ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(ctx); String sTimeout = prefs.GetString(ctx.GetString(Resource.String.app_timeout_key), ctx.GetString(Resource.String.clipboard_timeout_default)); @@ -61,8 +55,6 @@ namespace keepass2android return; } - ctx.StartService(new Intent(ctx, typeof(TimeoutService))); - long triggerTime = Java.Lang.JavaSystem.CurrentTimeMillis() + timeout; AlarmManager am = (AlarmManager)ctx.GetSystemService(Context.AlarmService); @@ -76,69 +68,25 @@ namespace keepass2android Kp2aLog.Log("Timeout cancel"); am.Cancel(BuildIntent(ctx)); - - ctx.StopService(new Intent(ctx, typeof(TimeoutService))); - } } - public static void Pause(Activity act) { - // Record timeout time in case timeout service is killed - long time = Java.Lang.JavaSystem.CurrentTimeMillis(); - - ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(act); - ISharedPreferencesEditor edit = prefs.Edit(); - edit.PutLong(act.GetString(Resource.String.timeout_key), time); - - Kp2aLog.Log("Pause: start at " + time); - - EditorCompat.Apply(edit); - - if ( App.Kp2a.GetDb().Open ) { + public static void Pause(Activity act) + { + if ( App.Kp2a.DatabaseIsUnlocked ) + { Timeout.Start(act); } } - public static void Resume(Activity act) { - if ( App.Kp2a.GetDb().Loaded ) { + public static void Resume(Activity act) + { + if ( App.Kp2a.GetDb().Loaded ) + { Timeout.Cancel(act); } - - - // Check whether the timeout has expired - long curTime = Java.Lang.JavaSystem.CurrentTimeMillis(); - - ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(act); - long timeoutStart = prefs.GetLong(act.GetString(Resource.String.timeout_key), -1); - Kp2aLog.Log("timeoutStart=" + timeoutStart); - // The timeout never started - if (timeoutStart == -1) { - return; - } - - - String sTimeout = prefs.GetString(act.GetString(Resource.String.app_timeout_key), act.GetString(Resource.String.clipboard_timeout_default)); - long timeout; - if (!long.TryParse(sTimeout, out timeout) || (timeout == -1)) - { - Kp2aLog.Log("exit with timeout=" + timeout + "/"+sTimeout); - // We are set to never timeout - return; - } - - long diff = curTime - timeoutStart; - if (diff >= timeout) - { - // We have timed out - Kp2aLog.Log("Shutdown due to " + diff + ">=" + timeout); - App.Kp2a.SetShutdown(); - } - else - { - Kp2aLog.Log("No shutdown due to " + diff + "<" + timeout); - } } static bool IocChanged(IOConnectionInfo ioc, IOConnectionInfo other) @@ -148,11 +96,10 @@ namespace keepass2android } public static bool CheckShutdown(Activity act, IOConnectionInfo ioc) { - if (( App.Kp2a.GetDb().Loaded && (App.Kp2a.IsShutdown() || App.Kp2a.GetDb().Locked) ) + if (( !App.Kp2a.DatabaseIsUnlocked ) || (IocChanged(ioc, App.Kp2a.GetDb().Ioc))) //file was changed from ActionSend-Intent { - act.SetResult(KeePass.ExitLock); - act.Finish(); + App.Kp2a.LockDatabase(); return true; } return false;