implement UI updates after background sync for Group activity and Entry activity

This commit is contained in:
Philipp Crocoll
2025-05-13 21:34:06 +02:00
parent 41e6e67e87
commit 400e171bc5
9 changed files with 273 additions and 91 deletions

View File

@@ -4,6 +4,7 @@ using System.IO;
using System.Text; using System.Text;
using Android.App; using Android.App;
using Android.Content; using Android.Content;
using Android.OS;
using KeePassLib.Serialization; using KeePassLib.Serialization;
using keepass2android.Io; using keepass2android.Io;
using KeePass.Util; using KeePass.Util;
@@ -12,9 +13,8 @@ namespace keepass2android
{ {
public class SynchronizeCachedDatabase: OperationWithFinishHandler public class SynchronizeCachedDatabase: OperationWithFinishHandler
{ {
private readonly Context _context;
private readonly IKp2aApp _app; private readonly IKp2aApp _app;
private SaveDb _saveDb;
public SynchronizeCachedDatabase(IKp2aApp app, OnOperationFinishedHandler operationFinishedHandler) public SynchronizeCachedDatabase(IKp2aApp app, OnOperationFinishedHandler operationFinishedHandler)
: base(app, operationFinishedHandler) : base(app, operationFinishedHandler)
@@ -70,7 +70,7 @@ namespace keepass2android
) )
{ {
//conflict! need to merge //conflict! need to merge
_saveDb = new SaveDb(_app, new ActionOnOperationFinished(_app, (success, result, activity) => var _saveDb = new SaveDb(_app, new ActionOnOperationFinished(_app, (success, result, activity) =>
{ {
if (!success) if (!success)
{ {
@@ -80,7 +80,6 @@ namespace keepass2android
{ {
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully)); Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
} }
_saveDb = null;
}), _app.CurrentDb, false, remoteData); }), _app.CurrentDb, false, remoteData);
_saveDb.SetStatusLogger(StatusLogger); _saveDb.SetStatusLogger(StatusLogger);
_saveDb.DoNotSetStatusLoggerMessage = true; //Keep "sync db" as main message _saveDb.DoNotSetStatusLoggerMessage = true; //Keep "sync db" as main message
@@ -93,10 +92,32 @@ namespace keepass2android
} }
else else
{ {
//only the remote file was modified -> reload database. //only the remote file was modified -> reload database.
//note: it's best to lock the database and do a complete reload here (also better for UI consistency in case something goes wrong etc.) var onFinished = new ActionOnOperationFinished(_app, (success, result, activity) =>
_app.TriggerReload(_context, (bool result) => Finish(result)); {
} if (!success)
{
Finish(false, result);
}
else
{
new Handler(Looper.MainLooper).Post(() =>
{
_app.CurrentDb.UpdateGlobals();
_app.MarkAllGroupsAsDirty();
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
});
}
});
var _loadDb = new LoadDb(_app, ioc, Task.FromResult(remoteData), _app.CurrentDb.KpDatabase.MasterKey, null, onFinished, true, false);
_loadDb.SetStatusLogger(StatusLogger);
_loadDb.DoNotSetStatusLoggerMessage = true; //Keep "sync db" as main message
_loadDb.SyncInBackground = false;
_loadDb.Run();
}
} }
else else
{ {
@@ -124,10 +145,5 @@ namespace keepass2android
} }
public void JoinWorkerThread()
{
if (_saveDb != null)
_saveDb.JoinWorkerThread();
}
} }
} }

View File

@@ -35,12 +35,15 @@ namespace keepass2android
private readonly IOConnectionInfo _ioc; private readonly IOConnectionInfo _ioc;
private readonly Task<MemoryStream> _databaseData; private readonly Task<MemoryStream> _databaseData;
private readonly CompositeKey _compositeKey; private readonly CompositeKey _compositeKey;
private readonly string _keyfileOrProvider; private readonly string? _keyfileOrProvider;
private readonly IKp2aApp _app; private readonly IKp2aApp _app;
private readonly bool _rememberKeyfile; private readonly bool _rememberKeyfile;
IDatabaseFormat _format; IDatabaseFormat _format;
public LoadDb(Activity activity, IKp2aApp app, IOConnectionInfo ioc, Task<MemoryStream> databaseData, CompositeKey compositeKey, String keyfileOrProvider, OnOperationFinishedHandler operationFinishedHandler, bool updateLastUsageTimestamp, bool makeCurrent): base(app, operationFinishedHandler) public bool DoNotSetStatusLoggerMessage = false;
public bool SyncInBackground { get; set; }
public LoadDb(IKp2aApp app, IOConnectionInfo ioc, Task<MemoryStream> databaseData, CompositeKey compositeKey, String keyfileOrProvider, OnOperationFinishedHandler operationFinishedHandler, bool updateLastUsageTimestamp, bool makeCurrent): base(app, operationFinishedHandler)
{ {
_app = app; _app = app;
_ioc = ioc; _ioc = ioc;
@@ -49,8 +52,9 @@ namespace keepass2android
_keyfileOrProvider = keyfileOrProvider; _keyfileOrProvider = keyfileOrProvider;
_updateLastUsageTimestamp = updateLastUsageTimestamp; _updateLastUsageTimestamp = updateLastUsageTimestamp;
_makeCurrent = makeCurrent; _makeCurrent = makeCurrent;
_rememberKeyfile = app.GetBooleanPreference(PreferenceKey.remember_keyfile); _rememberKeyfile = app.GetBooleanPreference(PreferenceKey.remember_keyfile);
} SyncInBackground = _app.SyncInBackgroundPreference;
}
protected bool success = false; protected bool success = false;
private bool _updateLastUsageTimestamp; private bool _updateLastUsageTimestamp;
@@ -62,15 +66,22 @@ namespace keepass2android
{ {
try try
{ {
//make sure the file data is stored in the recent files list even if loading fails if (_keyfileOrProvider != null)
SaveFileData(_ioc, _keyfileOrProvider); {
//make sure the file data is stored in the recent files list even if loading fails
SaveFileData(_ioc, _keyfileOrProvider);
}
var fileStorage = _app.GetFileStorage(_ioc); var fileStorage = _app.GetFileStorage(_ioc);
bool requiresSubsequentSync = false; bool requiresSubsequentSync = false;
StatusLogger.UpdateMessage(UiStringKey.loading_database); if (!DoNotSetStatusLoggerMessage)
{
StatusLogger.UpdateMessage(UiStringKey.loading_database);
}
//get the stream data into a single stream variable (databaseStream) regardless whether its preloaded or not: //get the stream data into a single stream variable (databaseStream) regardless whether its preloaded or not:
MemoryStream preloadedMemoryStream = _databaseData == null ? null : _databaseData.Result; MemoryStream preloadedMemoryStream = _databaseData == null ? null : _databaseData.Result;
MemoryStream databaseStream; MemoryStream databaseStream;
@@ -167,7 +178,6 @@ namespace keepass2android
{ {
Database newDb = _app.LoadDatabase(_ioc, workingCopy, _compositeKey, StatusLogger, _format, _makeCurrent); Database newDb = _app.LoadDatabase(_ioc, workingCopy, _compositeKey, StatusLogger, _format, _makeCurrent);
Kp2aLog.Log("LoadDB OK"); Kp2aLog.Log("LoadDB OK");
if (requiresSubsequentSync) if (requiresSubsequentSync)
{ {
var syncTask = new SynchronizeCachedDatabase(_app, new ActionOnOperationFinished(_app, var syncTask = new SynchronizeCachedDatabase(_app, new ActionOnOperationFinished(_app,

View File

@@ -111,6 +111,45 @@ namespace keepass2android
protected override View? SnackbarAnchorView => FindViewById(Resource.Id.main_content); protected override View? SnackbarAnchorView => FindViewById(Resource.Id.main_content);
public class UpdateEntryActivityBroadcastReceiver : BroadcastReceiver
{
private readonly EntryActivity _activity;
public UpdateEntryActivityBroadcastReceiver(EntryActivity activity)
{
_activity = activity;
}
public override void OnReceive(Context? context, Intent? intent)
{
if (intent?.Action == Intents.DataUpdated)
{
_activity.OnDataUpdated();
}
}
}
private void OnDataUpdated()
{
if (Entry == null)
{
return;
}
var entryUId = Entry.Uuid;
if (!App.Kp2a.CurrentDb.EntriesById.ContainsKey(entryUId))
{
Finish();
return;
}
var newEntry = App.Kp2a.CurrentDb.EntriesById[entryUId];
if (!newEntry.EqualsEntry(Entry, PwCompareOptions.None, MemProtCmpMode.Full))
{
Recreate();
}
}
public static void Launch(Activity act, PwEntry pw, int pos, AppTask appTask, ActivityFlags? flags = null, int historyIndex=-1) public static void Launch(Activity act, PwEntry pw, int pos, AppTask appTask, ActivityFlags? flags = null, int historyIndex=-1)
{ {
Intent i = new Intent(act, typeof(EntryActivity)); Intent i = new Intent(act, typeof(EntryActivity));
@@ -502,7 +541,13 @@ namespace keepass2android
//the rest of the things to do depends on the current app task: //the rest of the things to do depends on the current app task:
AppTask.CompleteOnCreateEntryActivity(this, notifyPluginsOnOpenThread); AppTask.CompleteOnCreateEntryActivity(this, notifyPluginsOnOpenThread);
}
_dataUpdatedIntentReceiver = new UpdateEntryActivityBroadcastReceiver(this);
IntentFilter filter = new IntentFilter();
filter.AddAction(Intents.DataUpdated);
ContextCompat.RegisterReceiver(this, _dataUpdatedIntentReceiver, filter, (int)ReceiverFlags.Exported);
}
private void RemoveFromHistory() private void RemoveFromHistory()
{ {
@@ -1083,7 +1128,9 @@ namespace keepass2android
UnregisterReceiver(_pluginActionReceiver); UnregisterReceiver(_pluginActionReceiver);
if (_pluginFieldReceiver != null) if (_pluginFieldReceiver != null)
UnregisterReceiver(_pluginFieldReceiver); UnregisterReceiver(_pluginFieldReceiver);
base.OnDestroy(); if (_dataUpdatedIntentReceiver != null)
UnregisterReceiver(_dataUpdatedIntentReceiver);
base.OnDestroy();
} }
private void NotifyPluginsOnClose() private void NotifyPluginsOnClose()
@@ -1359,6 +1406,7 @@ namespace keepass2android
} }
bool isPaused = false; bool isPaused = false;
private UpdateEntryActivityBroadcastReceiver _dataUpdatedIntentReceiver;
protected override void OnPause() protected override void OnPause()
{ {

View File

@@ -533,10 +533,9 @@ namespace keepass2android
} }
}); });
//make sure we can close the EntryEditActivity activity even if the app went to background till we get to the OnOperationFinishedHandler Action //make sure we can close the EntryEditActivity activity even if the app went to background till we get to the OnOperationFinishedHandler Action
closeOrShowError.AllowInactiveActivity = true;
ActionOnOperationFinished afterAddEntry = new ActionOnOperationFinished(App.Kp2a, (success, message, activity) =>
ActionOnOperationFinished afterAddEntry = new ActionOnOperationFinished(App.Kp2a, (success, message, activity) =>
{ {
if (success && activity is EntryEditActivity entryEditActivity) if (success && activity is EntryEditActivity entryEditActivity)
AppTask.AfterAddNewEntry(entryEditActivity, newEntry); AppTask.AfterAddNewEntry(entryEditActivity, newEntry);

View File

@@ -45,6 +45,7 @@ using AndroidX.AppCompat.Widget;
using Google.Android.Material.Dialog; using Google.Android.Material.Dialog;
using keepass2android.views; using keepass2android.views;
using SearchView = AndroidX.AppCompat.Widget.SearchView; using SearchView = AndroidX.AppCompat.Widget.SearchView;
using AndroidX.Core.Content;
namespace keepass2android namespace keepass2android
{ {
@@ -275,6 +276,7 @@ namespace keepass2android
private IMenuItem searchItem; private IMenuItem searchItem;
private IMenuItem searchItemDummy; private IMenuItem searchItemDummy;
private bool isPaused; private bool isPaused;
private UpdateGroupBaseActivityBroadcastReceiver _dataUpdatedIntentReceiver;
protected override void OnResume() protected override void OnResume()
{ {
@@ -746,9 +748,10 @@ namespace keepass2android
_dataUpdatedIntentReceiver = new UpdateGroupBaseActivityBroadcastReceiver(this);
IntentFilter filter = new IntentFilter();
filter.AddAction(Intents.DataUpdated);
ContextCompat.RegisterReceiver(this, _dataUpdatedIntentReceiver, filter, (int)ReceiverFlags.Exported);
SetResult(KeePass.ExitNormal); SetResult(KeePass.ExitNormal);
@@ -1034,6 +1037,13 @@ namespace keepass2android
} }
} }
protected override void OnDestroy()
{
UnregisterReceiver(_dataUpdatedIntentReceiver);
base.OnDestroy();
}
public override bool OnCreateOptionsMenu(IMenu menu) public override bool OnCreateOptionsMenu(IMenu menu)
{ {
@@ -1417,6 +1427,50 @@ namespace keepass2android
return FindViewById<BackgroundOperationContainer>(Resource.Id.background_ops_container); return FindViewById<BackgroundOperationContainer>(Resource.Id.background_ops_container);
} }
} }
public void OnDataUpdated()
{
if (Group == null || FragmentManager.IsDestroyed)
{
return;
}
var groupId = Group.Uuid;
if (!App.Kp2a.CurrentDb.GroupsById.ContainsKey(groupId))
{
Finish();
return;
}
Group = App.Kp2a.CurrentDb.GroupsById[groupId];
var fragment = FragmentManager.FindFragmentById<GroupListFragment>(Resource.Id.list_fragment);
if (fragment == null)
{
throw new Exception("did not find fragment");
}
fragment.ListAdapter = new PwGroupListAdapter(this, Group);
SetGroupIcon();
SetGroupTitle();
ListAdapter?.NotifyDataSetChanged();
}
}
public class UpdateGroupBaseActivityBroadcastReceiver : BroadcastReceiver
{
private readonly GroupBaseActivity _groupBaseActivity;
public UpdateGroupBaseActivityBroadcastReceiver(GroupBaseActivity groupBaseActivity)
{
_groupBaseActivity = groupBaseActivity;
}
public override void OnReceive(Context? context, Intent? intent)
{
if (intent?.Action == Intents.DataUpdated)
{
_groupBaseActivity.OnDataUpdated();
}
}
} }
public class GroupListFragment : ListFragment, AbsListView.IMultiChoiceModeListener public class GroupListFragment : ListFragment, AbsListView.IMultiChoiceModeListener

View File

@@ -38,7 +38,7 @@ namespace keepass2android
protected const string NoLockCheck = "NO_LOCK_CHECK"; protected const string NoLockCheck = "NO_LOCK_CHECK";
protected IOConnectionInfo _ioc; protected IOConnectionInfo _ioc;
private BroadcastReceiver _intentReceiver; private BroadcastReceiver _lockCloseIntentReceiver;
private ActivityDesign _design; private ActivityDesign _design;
public LockCloseActivity() public LockCloseActivity()
@@ -66,11 +66,11 @@ namespace keepass2android
if (Intent.GetBooleanExtra(NoLockCheck, false)) if (Intent.GetBooleanExtra(NoLockCheck, false))
return; return;
_intentReceiver = new LockCloseActivityBroadcastReceiver(this); _lockCloseIntentReceiver = new LockCloseActivityBroadcastReceiver(this);
IntentFilter filter = new IntentFilter(); IntentFilter filter = new IntentFilter();
filter.AddAction(Intents.DatabaseLocked); filter.AddAction(Intents.DatabaseLocked);
filter.AddAction(Intent.ActionScreenOff); filter.AddAction(Intent.ActionScreenOff);
ContextCompat.RegisterReceiver(this, _intentReceiver, filter, (int)ReceiverFlags.Exported); ContextCompat.RegisterReceiver(this, _lockCloseIntentReceiver, filter, (int)ReceiverFlags.Exported);
} }
protected override void OnDestroy() protected override void OnDestroy()
@@ -79,7 +79,7 @@ namespace keepass2android
{ {
try try
{ {
UnregisterReceiver(_intentReceiver); UnregisterReceiver(_lockCloseIntentReceiver);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -1444,7 +1444,7 @@ namespace keepass2android
LoadDb task = (KeyProviderTypes.Contains(KeyProviders.Otp)) LoadDb task = (KeyProviderTypes.Contains(KeyProviders.Otp))
? new SaveOtpAuxFileAndLoadDb(App.Kp2a, _ioConnection, _loadDbFileTask, compositeKey, GetKeyProviderString(), ? new SaveOtpAuxFileAndLoadDb(App.Kp2a, _ioConnection, _loadDbFileTask, compositeKey, GetKeyProviderString(),
onOperationFinishedHandler, this, true, _makeCurrent) onOperationFinishedHandler, this, true, _makeCurrent)
: new LoadDb(this, App.Kp2a, _ioConnection, _loadDbFileTask, compositeKey, GetKeyProviderString(), onOperationFinishedHandler,true, _makeCurrent); : new LoadDb(App.Kp2a, _ioConnection, _loadDbFileTask, compositeKey, GetKeyProviderString(), onOperationFinishedHandler,true, _makeCurrent);
_loadDbFileTask = null; // prevent accidental re-use _loadDbFileTask = null; // prevent accidental re-use
new BlockingOperationRunner(App.Kp2a, task).Run(); new BlockingOperationRunner(App.Kp2a, task).Run();
@@ -1886,7 +1886,7 @@ namespace keepass2android
Handler handler = new Handler(); Handler handler = new Handler();
OnOperationFinishedHandler onOperationFinishedHandler = new AfterLoad(handler, this, _ioConnection); OnOperationFinishedHandler onOperationFinishedHandler = new AfterLoad(handler, this, _ioConnection);
_performingLoad = true; _performingLoad = true;
LoadDb task = new LoadDb(this, App.Kp2a, _ioConnection, _loadDbFileTask, compositeKeyForImmediateLoad, GetKeyProviderString(), LoadDb task = new LoadDb(App.Kp2a, _ioConnection, _loadDbFileTask, compositeKeyForImmediateLoad, GetKeyProviderString(),
onOperationFinishedHandler, false, _makeCurrent); onOperationFinishedHandler, false, _makeCurrent);
_loadDbFileTask = null; // prevent accidental re-use _loadDbFileTask = null; // prevent accidental re-use
new BlockingOperationRunner(App.Kp2a, task).Run(); new BlockingOperationRunner(App.Kp2a, task).Run();
@@ -2276,7 +2276,7 @@ namespace keepass2android
private readonly PasswordActivity _act; private readonly PasswordActivity _act;
public SaveOtpAuxFileAndLoadDb(IKp2aApp app, IOConnectionInfo ioc, Task<MemoryStream> databaseData, CompositeKey compositeKey, string keyfileOrProvider, OnOperationFinishedHandler operationFinishedHandler, PasswordActivity act, bool updateLastUsageTimestamp, bool makeCurrent) : base(act, app, ioc, databaseData, compositeKey, keyfileOrProvider, operationFinishedHandler,updateLastUsageTimestamp,makeCurrent) public SaveOtpAuxFileAndLoadDb(IKp2aApp app, IOConnectionInfo ioc, Task<MemoryStream> databaseData, CompositeKey compositeKey, string keyfileOrProvider, OnOperationFinishedHandler operationFinishedHandler, PasswordActivity act, bool updateLastUsageTimestamp, bool makeCurrent) : base(app, ioc, databaseData, compositeKey, keyfileOrProvider, operationFinishedHandler,updateLastUsageTimestamp,makeCurrent)
{ {
_act = act; _act = act;
} }

View File

@@ -151,11 +151,17 @@ namespace keepass2android
BroadcastDatabaseAction(LocaleManager.LocalizedAppContext, Strings.ActionCloseDatabase); BroadcastDatabaseAction(LocaleManager.LocalizedAppContext, Strings.ActionCloseDatabase);
// Couldn't quick-lock, so unload database(s) instead // Couldn't quick-lock, so unload database(s) instead
_openAttempts.Clear();
_openDatabases.Clear(); lock (_openDatabasesLock)
_currentDatabase = null; {
LastOpenedEntry = null; _openAttempts.Clear();
QuickLocked = false; _openDatabases.Clear();
_currentDatabase = null;
LastOpenedEntry = null;
QuickLocked = false;
}
} }
} }
else else
@@ -193,39 +199,59 @@ namespace keepass2android
public Database LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, CompositeKey compositeKey, IKp2aStatusLogger statusLogger, IDatabaseFormat databaseFormat, bool makeCurrent) public Database LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, CompositeKey compositeKey, IKp2aStatusLogger statusLogger, IDatabaseFormat databaseFormat, bool makeCurrent)
{ {
var prefs = PreferenceManager.GetDefaultSharedPreferences(LocaleManager.LocalizedAppContext);
var createBackup = prefs.GetBoolean(LocaleManager.LocalizedAppContext.GetString(Resource.String.CreateBackups_key), true)
var prefs = PreferenceManager.GetDefaultSharedPreferences(LocaleManager.LocalizedAppContext);
var createBackup =
prefs.GetBoolean(LocaleManager.LocalizedAppContext.GetString(Resource.String.CreateBackups_key),
true)
&& !(new LocalFileStorage(this).IsLocalBackup(ioConnectionInfo)); && !(new LocalFileStorage(this).IsLocalBackup(ioConnectionInfo));
MemoryStream backupCopy = new MemoryStream(); MemoryStream backupCopy = new MemoryStream();
if (createBackup) if (createBackup)
{ {
memoryStream.CopyTo(backupCopy); memoryStream.CopyTo(backupCopy);
backupCopy.Seek(0, SeekOrigin.Begin); backupCopy.Seek(0, SeekOrigin.Begin);
//reset stream if we need to reuse it later: //reset stream if we need to reuse it later:
memoryStream.Seek(0, SeekOrigin.Begin); memoryStream.Seek(0, SeekOrigin.Begin);
} }
foreach (Database openDb in _openDatabases)
{
if (openDb.Ioc.IsSameFileAs(ioConnectionInfo))
{
//TODO check this earlier and simply open the database's root group
throw new Exception("Database already loaded!");
}
}
_openAttempts.Add(ioConnectionInfo);
var newDb = new Database(new DrawableFactory(), this); _openAttempts.Add(ioConnectionInfo);
var newDb = new Database(new DrawableFactory(), this);
newDb.LoadData(this, ioConnectionInfo, memoryStream, compositeKey, statusLogger, databaseFormat); newDb.LoadData(this, ioConnectionInfo, memoryStream, compositeKey, statusLogger, databaseFormat);
if ((_currentDatabase == null) || makeCurrent) lock (_openDatabasesLock)
_currentDatabase = newDb; {
_openDatabases.Add(newDb); if ((_currentDatabase == null) || makeCurrent) _currentDatabase = newDb;
bool replacedOpenDatabase = false;
for (int i = 0; i < _openDatabases.Count; i++)
{
if (_openDatabases[i].Ioc.IsSameFileAs(ioConnectionInfo))
{
if (_currentDatabase == _openDatabases[i])
{
_currentDatabase = newDb;
}
replacedOpenDatabase = true;
_openDatabases[i] = newDb;
break;
}
}
if (!replacedOpenDatabase)
{
_openDatabases.Add(newDb);
}
}
@@ -285,21 +311,27 @@ namespace keepass2android
public void CloseDatabase(Database db) public void CloseDatabase(Database db)
{ {
if (!_openDatabases.Contains(db)) lock (_openDatabasesLock)
throw new Exception("Cannot close database which is not open!"); {
if (_openDatabases.Count == 1) //TODO check that Lock() below works without a deadlock
{ if (!_openDatabases.Contains(db))
Lock(false); throw new Exception("Cannot close database which is not open!");
return; if (_openDatabases.Count == 1)
} {
if (LastOpenedEntry != null && db.EntriesById.ContainsKey(LastOpenedEntry.Uuid)) Lock(false);
{ return;
LastOpenedEntry = null; }
}
if (LastOpenedEntry != null && db.EntriesById.ContainsKey(LastOpenedEntry.Uuid))
_openDatabases.Remove(db); {
if (_currentDatabase == db) LastOpenedEntry = null;
_currentDatabase = _openDatabases.First(); }
_openDatabases.Remove(db);
if (_currentDatabase == db)
_currentDatabase = _openDatabases.First();
}
UpdateOngoingNotification(); UpdateOngoingNotification();
//TODO broadcast event so affected activities can close/update? //TODO broadcast event so affected activities can close/update?
} }
@@ -376,12 +408,20 @@ namespace keepass2android
private readonly List<IOConnectionInfo> _openAttempts = new List<IOConnectionInfo>(); //stores which files have been attempted to open. Used to avoid that we repeatedly try to load files which failed to load. private readonly List<IOConnectionInfo> _openAttempts = new List<IOConnectionInfo>(); //stores which files have been attempted to open. Used to avoid that we repeatedly try to load files which failed to load.
private readonly List<Database> _openDatabases = new List<Database>(); private readonly List<Database> _openDatabases = new List<Database>();
private readonly object _openDatabasesLock = new object();
private readonly List<IOConnectionInfo> _childDatabases = new List<IOConnectionInfo>(); //list of databases which were opened as child databases private readonly List<IOConnectionInfo> _childDatabases = new List<IOConnectionInfo>(); //list of databases which were opened as child databases
private Database _currentDatabase; private Database _currentDatabase;
public IEnumerable<Database> OpenDatabases public IEnumerable<Database> OpenDatabases
{ {
get { return _openDatabases; } get
{
lock (_openDatabasesLock)
{
//avoid concurrent access to _openDatabases
return new List<Database>(_openDatabases);
}
}
} }
internal ChallengeXCKey _currentlyWaitingXcKey; internal ChallengeXCKey _currentlyWaitingXcKey;
@@ -415,8 +455,9 @@ namespace keepass2android
DirtyGroups.Add(group); DirtyGroups.Add(group);
} }
var intent = new Intent(Intents.DataUpdated);
} App.Context.SendBroadcast(intent);
}
/// <summary> /// <summary>
@@ -1345,13 +1386,22 @@ namespace keepass2android
public Database TryFindDatabaseForElement(IStructureItem element) public Database TryFindDatabaseForElement(IStructureItem element)
{ {
foreach (var db in OpenDatabases) try
{ {
//we compare UUIDs and not by reference. this is more robust and works with history items as well foreach (var db in OpenDatabases)
if (db.Elements.Any(e => e.Uuid?.Equals(element.Uuid) == true)) {
return db; //we compare UUIDs and not by reference. this is more robust and works with history items as well
if (db.Elements.Any(e => e.Uuid?.Equals(element.Uuid) == true))
{
return db;
}
}
} }
return null; catch (Exception e)
{
Kp2aLog.LogUnexpectedError(e);
}
return null;
} }
public void RegisterChildDatabase(IOConnectionInfo ioc) public void RegisterChildDatabase(IOConnectionInfo ioc)

View File

@@ -37,8 +37,13 @@ namespace keepass2android
/// <summary>This intent will be broadcast once the database has been locked. Sensitive information displayed should be hidden and unloaded.</summary> /// <summary>This intent will be broadcast once the database has been locked. Sensitive information displayed should be hidden and unloaded.</summary>
public const String DatabaseLocked = "keepass2android." + AppNames.PackagePart + ".database_locked"; public const String DatabaseLocked = "keepass2android." + AppNames.PackagePart + ".database_locked";
/// <summary>This intent will be broadcast once the keyboard data has been cleared</summary> /// <summary>
public const String KeyboardCleared = "keepass2android." + AppNames.PackagePart + ".keyboard_cleared"; /// Signals that the loaded data was updated, e.g. by reloading during sync. All UI elements should be refreshed.
/// </summary>
public const String DataUpdated = "keepass2android." + AppNames.PackagePart + ".data_updated";
/// <summary>This intent will be broadcast once the keyboard data has been cleared</summary>
public const String KeyboardCleared = "keepass2android." + AppNames.PackagePart + ".keyboard_cleared";
public const String CopyUsername = "keepass2android.copy_username"; public const String CopyUsername = "keepass2android.copy_username";
public const String CopyPassword = "keepass2android.copy_password"; public const String CopyPassword = "keepass2android.copy_password";