allow to cancel background operations manually or when another operation starts; block database updates in certain activities, e.g. EntryEdit;
This commit is contained in:
@@ -122,4 +122,20 @@ public class BackgroundOperationRunner
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void CancelAll()
|
||||
{
|
||||
lock (_taskQueueLock)
|
||||
{
|
||||
if (_thread != null)
|
||||
{
|
||||
_thread.Interrupt();
|
||||
_thread = null;
|
||||
_statusLogger?.EndLogging();
|
||||
}
|
||||
|
||||
_taskQueue.Clear();
|
||||
_currentlyRunningTask = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,6 +78,9 @@ namespace keepass2android
|
||||
|
||||
SetupProgressDialog(app);
|
||||
|
||||
app.CancelBackgroundOperations();
|
||||
|
||||
|
||||
// Set code to run when this is finished
|
||||
_task.operationFinishedHandler = new AfterTask(app, task.operationFinishedHandler, _handler, this);
|
||||
|
||||
|
||||
@@ -52,7 +52,9 @@ namespace keepass2android
|
||||
/// <summary>
|
||||
/// Loads the specified data as the currently open database, as unlocked.
|
||||
/// </summary>
|
||||
Database LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, CompositeKey compKey, IKp2aStatusLogger statusLogger, IDatabaseFormat databaseFormat, bool makeCurrent);
|
||||
Database LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, CompositeKey compKey,
|
||||
IKp2aStatusLogger statusLogger, IDatabaseFormat databaseFormat, bool makeCurrent,
|
||||
IDatabaseModificationWatcher modificationWatcher);
|
||||
|
||||
|
||||
HashSet<PwGroup> DirtyGroups { get; }
|
||||
@@ -134,12 +136,12 @@ namespace keepass2android
|
||||
bool CheckForDuplicateUuids { get; }
|
||||
#if !NoNet && !EXCLUDE_JAVAFILESTORAGE
|
||||
ICertificateErrorHandler CertificateErrorHandler { get; }
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
bool SyncInBackgroundPreference { get; set; }
|
||||
void StartBackgroundSyncService();
|
||||
|
||||
ReaderWriterLockSlim DatabasesBackgroundModificationLock { get; }
|
||||
bool CancelBackgroundOperations();
|
||||
}
|
||||
}
|
||||
@@ -111,6 +111,11 @@ namespace keepass2android.Io
|
||||
}
|
||||
|
||||
Java.Lang.Exception exception = e as Java.Lang.Exception;
|
||||
|
||||
if ((exception is Java.Lang.InterruptedException) || (exception is Java.IO.InterruptedIOException))
|
||||
{
|
||||
throw new Java.Lang.InterruptedException(exception.Message);
|
||||
}
|
||||
if (exception != null)
|
||||
{
|
||||
var ex = new Exception(exception.LocalizedMessage ??
|
||||
|
||||
@@ -79,7 +79,7 @@ public class ProgressUiAsStatusLoggerAdapter : IKp2aStatusLogger
|
||||
|
||||
public bool ContinueWork()
|
||||
{
|
||||
return true;
|
||||
return !Java.Lang.Thread.Interrupted();
|
||||
}
|
||||
|
||||
public void UpdateMessage(UiStringKey stringKey)
|
||||
|
||||
@@ -396,8 +396,6 @@ namespace keepass2android
|
||||
{
|
||||
PwGroupV3 toGroup = new PwGroupV3();
|
||||
toGroup.Name = fromGroup.Name;
|
||||
//todo remove
|
||||
Android.Util.Log.Debug("KP2A", "save kdb: group " + fromGroup.Name);
|
||||
|
||||
toGroup.TCreation = new PwDate(ConvertTime(fromGroup.CreationTime));
|
||||
toGroup.TLastAccess= new PwDate(ConvertTime(fromGroup.LastAccessTime));
|
||||
|
||||
@@ -8,92 +8,98 @@ using Android.OS;
|
||||
using KeePassLib.Serialization;
|
||||
using keepass2android.Io;
|
||||
using KeePass.Util;
|
||||
using Group.Pals.Android.Lib.UI.Filechooser.Utils;
|
||||
|
||||
namespace keepass2android
|
||||
{
|
||||
public class SynchronizeCachedDatabase: OperationWithFinishHandler
|
||||
{
|
||||
private readonly IKp2aApp _app;
|
||||
|
||||
private IDatabaseModificationWatcher _modificationWatcher;
|
||||
|
||||
public SynchronizeCachedDatabase(IKp2aApp app, OnOperationFinishedHandler operationFinishedHandler)
|
||||
|
||||
public SynchronizeCachedDatabase(IKp2aApp app, OnOperationFinishedHandler operationFinishedHandler, IDatabaseModificationWatcher modificationWatcher)
|
||||
: base(app, operationFinishedHandler)
|
||||
{
|
||||
_app = app;
|
||||
}
|
||||
{
|
||||
_app = app;
|
||||
_modificationWatcher = modificationWatcher;
|
||||
}
|
||||
|
||||
public override void Run()
|
||||
{
|
||||
try
|
||||
{
|
||||
IOConnectionInfo ioc = _app.CurrentDb.Ioc;
|
||||
IFileStorage fileStorage = _app.GetFileStorage(ioc);
|
||||
if (!(fileStorage is CachingFileStorage))
|
||||
{
|
||||
throw new Exception("Cannot sync a non-cached database!");
|
||||
}
|
||||
StatusLogger.UpdateMessage(UiStringKey.SynchronizingCachedDatabase);
|
||||
CachingFileStorage cachingFileStorage = (CachingFileStorage) fileStorage;
|
||||
try
|
||||
{
|
||||
IOConnectionInfo ioc = _app.CurrentDb.Ioc;
|
||||
IFileStorage fileStorage = _app.GetFileStorage(ioc);
|
||||
if (!(fileStorage is CachingFileStorage))
|
||||
{
|
||||
throw new Exception("Cannot sync a non-cached database!");
|
||||
}
|
||||
|
||||
//download file from remote location and calculate hash:
|
||||
StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.DownloadingRemoteFile));
|
||||
string hash;
|
||||
StatusLogger.UpdateMessage(UiStringKey.SynchronizingCachedDatabase);
|
||||
CachingFileStorage cachingFileStorage = (CachingFileStorage)fileStorage;
|
||||
|
||||
//TODO remove
|
||||
//download file from remote location and calculate hash:
|
||||
StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.DownloadingRemoteFile));
|
||||
string hash;
|
||||
|
||||
//TODO remove
|
||||
Thread.Sleep(5000);
|
||||
|
||||
MemoryStream remoteData;
|
||||
try
|
||||
{
|
||||
remoteData = cachingFileStorage.GetRemoteDataAndHash(ioc, out hash);
|
||||
Kp2aLog.Log("Checking for file change. Current hash = " + hash);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.RestoringRemoteFile));
|
||||
cachingFileStorage.UpdateRemoteFile(ioc, _app.GetBooleanPreference(PreferenceKey.UseFileTransactions));
|
||||
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
|
||||
Kp2aLog.Log("Checking for file change: file not found");
|
||||
return;
|
||||
}
|
||||
|
||||
//check if remote file was modified:
|
||||
MemoryStream remoteData;
|
||||
try
|
||||
{
|
||||
remoteData = cachingFileStorage.GetRemoteDataAndHash(ioc, out hash);
|
||||
Kp2aLog.Log("Checking for file change. Current hash = " + hash);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.RestoringRemoteFile));
|
||||
cachingFileStorage.UpdateRemoteFile(ioc,
|
||||
_app.GetBooleanPreference(PreferenceKey.UseFileTransactions));
|
||||
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
|
||||
Kp2aLog.Log("Checking for file change: file not found");
|
||||
return;
|
||||
}
|
||||
|
||||
//check if remote file was modified:
|
||||
var baseVersionHash = cachingFileStorage.GetBaseVersionHash(ioc);
|
||||
Kp2aLog.Log("Checking for file change. baseVersionHash = " + baseVersionHash);
|
||||
if (baseVersionHash != hash ||
|
||||
true//TODO remove
|
||||
)
|
||||
{
|
||||
//remote file is modified
|
||||
if (cachingFileStorage.HasLocalChanges(ioc)
|
||||
|| true //TODO remove
|
||||
)
|
||||
{
|
||||
//conflict! need to merge
|
||||
var _saveDb = new SaveDb(_app, new ActionOnOperationFinished(_app, (success, result, activity) =>
|
||||
{
|
||||
if (!success)
|
||||
{
|
||||
Finish(false, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
|
||||
}
|
||||
}), _app.CurrentDb, false, remoteData);
|
||||
if (baseVersionHash != hash ||
|
||||
true //TODO remove
|
||||
)
|
||||
{
|
||||
//remote file is modified
|
||||
if (cachingFileStorage.HasLocalChanges(ioc)
|
||||
|| false //TODO remove
|
||||
)
|
||||
{
|
||||
//conflict! need to merge
|
||||
var _saveDb = new SaveDb(_app, new ActionOnOperationFinished(_app,
|
||||
(success, result, activity) =>
|
||||
{
|
||||
if (!success)
|
||||
{
|
||||
Finish(false, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
|
||||
}
|
||||
}), _app.CurrentDb, false, remoteData, _modificationWatcher);
|
||||
_saveDb.SetStatusLogger(StatusLogger);
|
||||
_saveDb.DoNotSetStatusLoggerMessage = true; //Keep "sync db" as main message
|
||||
_saveDb.SyncInBackground = false;
|
||||
_saveDb.Run();
|
||||
_saveDb.Run();
|
||||
|
||||
_app.CurrentDb.UpdateGlobals();
|
||||
|
||||
_app.MarkAllGroupsAsDirty();
|
||||
}
|
||||
else
|
||||
{
|
||||
_app.MarkAllGroupsAsDirty();
|
||||
}
|
||||
else
|
||||
{
|
||||
//only the remote file was modified -> reload database.
|
||||
var onFinished = new ActionOnOperationFinished(_app, (success, result, activity) =>
|
||||
var onFinished = new ActionOnOperationFinished(_app, (success, result, activity) =>
|
||||
{
|
||||
if (!success)
|
||||
{
|
||||
@@ -111,32 +117,44 @@ namespace keepass2android
|
||||
|
||||
}
|
||||
});
|
||||
var _loadDb = new LoadDb(_app, ioc, Task.FromResult(remoteData), _app.CurrentDb.KpDatabase.MasterKey, null, onFinished, true, false);
|
||||
var _loadDb = new LoadDb(_app, ioc, Task.FromResult(remoteData),
|
||||
_app.CurrentDb.KpDatabase.MasterKey, null, onFinished, true, false, _modificationWatcher);
|
||||
_loadDb.SetStatusLogger(StatusLogger);
|
||||
_loadDb.DoNotSetStatusLoggerMessage = true; //Keep "sync db" as main message
|
||||
_loadDb.SyncInBackground = false;
|
||||
_loadDb.Run();
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//remote file is unmodified
|
||||
if (cachingFileStorage.HasLocalChanges(ioc))
|
||||
{
|
||||
//but we have local changes -> upload:
|
||||
StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.UploadingFile));
|
||||
cachingFileStorage.UpdateRemoteFile(ioc, _app.GetBooleanPreference(PreferenceKey.UseFileTransactions));
|
||||
StatusLogger.UpdateSubMessage("");
|
||||
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
|
||||
}
|
||||
else
|
||||
{
|
||||
//files are in sync: just set the result
|
||||
Finish(true, _app.GetResourceString(UiStringKey.FilesInSync));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//remote file is unmodified
|
||||
if (cachingFileStorage.HasLocalChanges(ioc))
|
||||
{
|
||||
//but we have local changes -> upload:
|
||||
StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.UploadingFile));
|
||||
cachingFileStorage.UpdateRemoteFile(ioc,
|
||||
_app.GetBooleanPreference(PreferenceKey.UseFileTransactions));
|
||||
StatusLogger.UpdateSubMessage("");
|
||||
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
|
||||
}
|
||||
else
|
||||
{
|
||||
//files are in sync: just set the result
|
||||
Finish(true, _app.GetResourceString(UiStringKey.FilesInSync));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Java.Lang.InterruptedException e)
|
||||
{
|
||||
Kp2aLog.LogUnexpectedError(e);
|
||||
//no Finish()
|
||||
}
|
||||
catch (Java.IO.InterruptedIOException e)
|
||||
{
|
||||
Kp2aLog.LogUnexpectedError(e);
|
||||
//no Finish()
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Kp2aLog.LogUnexpectedError(e);
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace keepass2android
|
||||
_app.CurrentDb.Elements.Add(Group);
|
||||
|
||||
// Commit to disk
|
||||
SaveDb save = new SaveDb(_app, _app.CurrentDb, operationFinishedHandler, DontSave);
|
||||
SaveDb save = new SaveDb(_app, _app.CurrentDb, operationFinishedHandler, DontSave, null);
|
||||
save.SetStatusLogger(StatusLogger);
|
||||
save.Run();
|
||||
}
|
||||
|
||||
@@ -311,7 +311,7 @@ namespace keepass2android
|
||||
_app.DirtyGroups.Add(templateGroup);
|
||||
|
||||
// Commit to disk
|
||||
SaveDb save = new SaveDb( _app, _app.CurrentDb, operationFinishedHandler);
|
||||
SaveDb save = new SaveDb(_app, _app.CurrentDb, operationFinishedHandler);
|
||||
save.SetStatusLogger(StatusLogger);
|
||||
save.Run();
|
||||
}
|
||||
@@ -335,7 +335,6 @@ namespace keepass2android
|
||||
_app.DirtyGroups.Add(_app.CurrentDb.KpDatabase.RootGroup);
|
||||
_app.CurrentDb.GroupsById[templateGroup.Uuid] = templateGroup;
|
||||
_app.CurrentDb.Elements.Add(templateGroup);
|
||||
|
||||
}
|
||||
addedEntries = new List<PwEntry>();
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ namespace keepass2android
|
||||
addTemplates.AddTemplates(out addedEntries);
|
||||
|
||||
// Commit changes
|
||||
SaveDb save = new SaveDb(_app, db, operationFinishedHandler, _dontSave);
|
||||
SaveDb save = new SaveDb(_app, db, operationFinishedHandler, _dontSave, null);
|
||||
save.SetStatusLogger(StatusLogger);
|
||||
_operationFinishedHandler = null;
|
||||
save.Run();
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
using Java.Lang;
|
||||
|
||||
namespace keepass2android;
|
||||
|
||||
public interface IDatabaseModificationWatcher
|
||||
{
|
||||
void BeforeModifyDatabases();
|
||||
void AfterModifyDatabases();
|
||||
}
|
||||
|
||||
public class NullDatabaseModificationWatcher : IDatabaseModificationWatcher
|
||||
{
|
||||
public void BeforeModifyDatabases() { }
|
||||
public void AfterModifyDatabases() { }
|
||||
}
|
||||
|
||||
public class BackgroundDatabaseModificationLocker(IKp2aApp app) : IDatabaseModificationWatcher
|
||||
{
|
||||
public void BeforeModifyDatabases()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (app.DatabasesBackgroundModificationLock.TryEnterWriteLock(TimeSpan.FromSeconds(0.1)))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (Java.Lang.Thread.Interrupted())
|
||||
{
|
||||
throw new InterruptedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AfterModifyDatabases()
|
||||
{
|
||||
app.DatabasesBackgroundModificationLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
@@ -238,7 +238,7 @@ namespace keepass2android
|
||||
}, operationFinishedHandler);
|
||||
|
||||
// Commit database
|
||||
SaveDb save = new SaveDb( App, Db, operationFinishedHandler, false);
|
||||
SaveDb save = new SaveDb( App, Db, operationFinishedHandler, false, null);
|
||||
save.ShowDatabaseIocInStatus = ShowDatabaseIocInStatus;
|
||||
|
||||
save.SetStatusLogger(StatusLogger);
|
||||
|
||||
@@ -43,9 +43,12 @@ namespace keepass2android
|
||||
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)
|
||||
public LoadDb(IKp2aApp app, IOConnectionInfo ioc, Task<MemoryStream> databaseData, CompositeKey compositeKey,
|
||||
string keyfileOrProvider, OnOperationFinishedHandler operationFinishedHandler,
|
||||
bool updateLastUsageTimestamp, bool makeCurrent, IDatabaseModificationWatcher modificationWatcher = null): base(app, operationFinishedHandler)
|
||||
{
|
||||
_app = app;
|
||||
_modificationWatcher = modificationWatcher ?? new NullDatabaseModificationWatcher();
|
||||
_app = app;
|
||||
_ioc = ioc;
|
||||
_databaseData = databaseData;
|
||||
_compositeKey = compositeKey;
|
||||
@@ -59,8 +62,9 @@ namespace keepass2android
|
||||
protected bool success = false;
|
||||
private bool _updateLastUsageTimestamp;
|
||||
private readonly bool _makeCurrent;
|
||||
private readonly IDatabaseModificationWatcher _modificationWatcher;
|
||||
|
||||
public override void Run()
|
||||
public override void Run()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -109,6 +113,11 @@ namespace keepass2android
|
||||
}
|
||||
}
|
||||
|
||||
if (!StatusLogger.ContinueWork())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//ok, try to load the database. Let's start with Kdbx format and retry later if that is the wrong guess:
|
||||
_format = new KdbxDatabaseFormat(KdbxDatabaseFormat.GetFormatToUse(fileStorage.GetFileExtension(_ioc)));
|
||||
TryLoad(databaseStream, requiresSubsequentSync);
|
||||
@@ -147,7 +156,13 @@ namespace keepass2android
|
||||
Finish(false, _app.GetResourceString(UiStringKey.DuplicateUuidsError) + " " + ExceptionUtil.GetErrorMessage(e) + _app.GetResourceString(UiStringKey.DuplicateUuidsErrorAdditional), false, Exception);
|
||||
return;
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (Java.Lang.InterruptedException)
|
||||
{
|
||||
Kp2aLog.Log("Load interrupted");
|
||||
//close without Finish()
|
||||
return;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (!(e is InvalidCompositeKeyException))
|
||||
Kp2aLog.LogUnexpectedError(e);
|
||||
@@ -173,10 +188,16 @@ namespace keepass2android
|
||||
workingCopy.Seek(0, SeekOrigin.Begin);
|
||||
//reset stream if we need to reuse it later:
|
||||
databaseStream.Seek(0, SeekOrigin.Begin);
|
||||
if (!StatusLogger.ContinueWork())
|
||||
{
|
||||
throw new Java.Lang.InterruptedException();
|
||||
}
|
||||
|
||||
//now let's go:
|
||||
try
|
||||
{
|
||||
Database newDb = _app.LoadDatabase(_ioc, workingCopy, _compositeKey, StatusLogger, _format, _makeCurrent);
|
||||
try
|
||||
{
|
||||
Database newDb =
|
||||
_app.LoadDatabase(_ioc, workingCopy, _compositeKey, StatusLogger, _format, _makeCurrent, _modificationWatcher);
|
||||
Kp2aLog.Log("LoadDB OK");
|
||||
if (requiresSubsequentSync)
|
||||
{
|
||||
@@ -184,16 +205,17 @@ namespace keepass2android
|
||||
(success, message, activeActivity) =>
|
||||
{
|
||||
if (!String.IsNullOrEmpty(message))
|
||||
_app.ShowMessage(activeActivity, message, success ? MessageSeverity.Info : MessageSeverity.Error);
|
||||
|
||||
})
|
||||
_app.ShowMessage(activeActivity, message,
|
||||
success ? MessageSeverity.Info : MessageSeverity.Error);
|
||||
|
||||
}), new BackgroundDatabaseModificationLocker(_app)
|
||||
);
|
||||
BackgroundOperationRunner.Instance.Run(_app, syncTask);
|
||||
}
|
||||
|
||||
Finish(true, _format.SuccessMessage);
|
||||
return newDb;
|
||||
}
|
||||
return newDb;
|
||||
}
|
||||
catch (OldFormatException)
|
||||
{
|
||||
_format = new KdbDatabaseFormat(_app);
|
||||
|
||||
@@ -131,14 +131,14 @@ namespace keepass2android.database.edit
|
||||
operationFinishedHandler.Run();
|
||||
return;
|
||||
}
|
||||
SaveDb saveDb = new SaveDb( _app, allDatabasesToSave[indexToSave], new ActionOnOperationFinished(_app, ContinueSave), false);
|
||||
SaveDb saveDb = new SaveDb( _app, allDatabasesToSave[indexToSave], new ActionOnOperationFinished(_app, ContinueSave), false, null);
|
||||
saveDb.SetStatusLogger(StatusLogger);
|
||||
saveDb.ShowDatabaseIocInStatus = allDatabasesToSave.Count > 1;
|
||||
saveDb.Run();
|
||||
}
|
||||
|
||||
|
||||
SaveDb save = new SaveDb(_app, allDatabasesToSave[0], new ActionOnOperationFinished(_app, ContinueSave), false);
|
||||
SaveDb save = new SaveDb(_app, allDatabasesToSave[0], new ActionOnOperationFinished(_app, ContinueSave), false, null);
|
||||
save.SetStatusLogger(StatusLogger);
|
||||
save.ShowDatabaseIocInStatus = allDatabasesToSave.Count > 1;
|
||||
save.Run();
|
||||
|
||||
@@ -34,12 +34,17 @@ using Thread = System.Threading.Thread;
|
||||
|
||||
namespace keepass2android
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Save the database. If the file has changed, ask the user if he wants to overwrite or sync.
|
||||
/// </summary>
|
||||
|
||||
public class SaveDb : OperationWithFinishHandler {
|
||||
public class SaveDb : OperationWithFinishHandler {
|
||||
private readonly IKp2aApp _app;
|
||||
private readonly Database _db;
|
||||
private readonly bool _dontSave;
|
||||
private bool requiresSubsequentSync = false; //if true, we need to sync the file after saving.
|
||||
private readonly IDatabaseModificationWatcher _modificationWatcher;
|
||||
private bool requiresSubsequentSync = false; //if true, we need to sync the file after saving.
|
||||
|
||||
public bool DoNotSetStatusLoggerMessage = false;
|
||||
|
||||
@@ -50,12 +55,13 @@ namespace keepass2android
|
||||
|
||||
private Java.Lang.Thread _workerThread;
|
||||
|
||||
public SaveDb(IKp2aApp app, Database db, OnOperationFinishedHandler operationFinishedHandler, bool dontSave)
|
||||
public SaveDb(IKp2aApp app, Database db, OnOperationFinishedHandler operationFinishedHandler, bool dontSave, IDatabaseModificationWatcher modificationWatcher)
|
||||
: base(app, operationFinishedHandler)
|
||||
{
|
||||
_db = db;
|
||||
_app = app;
|
||||
_dontSave = dontSave;
|
||||
_modificationWatcher = modificationWatcher;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -65,10 +71,11 @@ namespace keepass2android
|
||||
/// <param name="operationFinishedHandler"></param>
|
||||
/// <param name="dontSave"></param>
|
||||
/// <param name="streamForOrigFile">Stream for reading the data from the (changed) original location</param>
|
||||
public SaveDb(IKp2aApp app, OnOperationFinishedHandler operationFinishedHandler, Database db, bool dontSave, Stream streamForOrigFile)
|
||||
public SaveDb(IKp2aApp app, OnOperationFinishedHandler operationFinishedHandler, Database db, bool dontSave, Stream streamForOrigFile, IDatabaseModificationWatcher modificationWatcher = null)
|
||||
: base(app, operationFinishedHandler)
|
||||
{
|
||||
_db = db;
|
||||
_modificationWatcher = modificationWatcher ?? new NullDatabaseModificationWatcher();
|
||||
_db = db;
|
||||
_app = app;
|
||||
_dontSave = dontSave;
|
||||
_streamForOrigFile = streamForOrigFile;
|
||||
@@ -76,10 +83,12 @@ namespace keepass2android
|
||||
|
||||
}
|
||||
|
||||
public SaveDb(IKp2aApp app, Database db, OnOperationFinishedHandler operationFinishedHandler)
|
||||
public SaveDb(IKp2aApp app, Database db, OnOperationFinishedHandler operationFinishedHandler, IDatabaseModificationWatcher modificationWatcher = null)
|
||||
: base(app, operationFinishedHandler)
|
||||
{
|
||||
_app = app;
|
||||
|
||||
_modificationWatcher = modificationWatcher ?? new NullDatabaseModificationWatcher();
|
||||
_app = app;
|
||||
_db = db;
|
||||
_dontSave = false;
|
||||
SyncInBackground = _app.SyncInBackgroundPreference;
|
||||
@@ -135,11 +144,17 @@ namespace keepass2android
|
||||
}
|
||||
}
|
||||
|
||||
//TODO remove
|
||||
Thread.Sleep(5000);
|
||||
|
||||
|
||||
bool hasStreamForOrigFile = (_streamForOrigFile != null);
|
||||
bool hasChangeFast = hasStreamForOrigFile ||
|
||||
fileStorage.CheckForFileChangeFast(ioc, _db.LastFileVersion); //first try to use the fast change detection;
|
||||
bool hasHashChanged = !requiresSubsequentSync && (hasChangeFast ||
|
||||
bool hasHashChanged = !requiresSubsequentSync && (
|
||||
//TODO remove
|
||||
true ||
|
||||
hasChangeFast ||
|
||||
(FileHashChanged(ioc, _db.KpDatabase.HashOfFileOnDisk) ==
|
||||
FileHashChange.Changed)); //if that fails, hash the file and compare:
|
||||
|
||||
@@ -226,7 +241,7 @@ namespace keepass2android
|
||||
if (!System.String.IsNullOrEmpty(message))
|
||||
_app.ShowMessage(activeActivity, message, success ? MessageSeverity.Info : MessageSeverity.Error);
|
||||
|
||||
})
|
||||
}), new BackgroundDatabaseModificationLocker(_app)
|
||||
);
|
||||
BackgroundOperationRunner.Instance.Run(_app, syncTask);
|
||||
}
|
||||
@@ -238,12 +253,38 @@ namespace keepass2android
|
||||
//note: when synced, the file might be downloaded once again from the server. Caching the data
|
||||
//in the hashing function would solve this but increases complexity. I currently assume the files are
|
||||
//small.
|
||||
MergeIn(fileStorage, ioc);
|
||||
|
||||
try
|
||||
{
|
||||
_modificationWatcher.BeforeModifyDatabases();
|
||||
}
|
||||
catch (Java.Lang.InterruptedException)
|
||||
{
|
||||
// leave without Finish()
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
MergeIn(fileStorage, ioc);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_modificationWatcher.AfterModifyDatabases();
|
||||
|
||||
}
|
||||
|
||||
PerformSaveWithoutCheck(fileStorage, ioc);
|
||||
_db.UpdateGlobals();
|
||||
new Handler(Looper.MainLooper).Post(() =>
|
||||
{
|
||||
_db.UpdateGlobals();
|
||||
});
|
||||
|
||||
FinishWithSuccess();
|
||||
}
|
||||
|
||||
|
||||
private void RunInWorkerThread(Action runHandler)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace keepass2android
|
||||
|
||||
// Save Database
|
||||
_operationFinishedHandler = new AfterSave(_app, previousKey, previousMasterKeyChanged, pm, operationFinishedHandler);
|
||||
SaveDb save = new SaveDb(_app, _app.CurrentDb, operationFinishedHandler, _dontSave);
|
||||
SaveDb save = new SaveDb(_app, _app.CurrentDb, operationFinishedHandler, _dontSave, null);
|
||||
save.SetStatusLogger(StatusLogger);
|
||||
save.Run();
|
||||
}
|
||||
|
||||
@@ -123,7 +123,15 @@ namespace keepass2android
|
||||
|
||||
protected override void OnCreate(Bundle savedInstanceState)
|
||||
{
|
||||
base.OnCreate(savedInstanceState);
|
||||
//we don't want any background thread to update/reload the database while we're in this activity.
|
||||
if (!App.Kp2a.DatabasesBackgroundModificationLock.TryEnterReadLock(TimeSpan.FromSeconds(5)))
|
||||
{
|
||||
App.Kp2a.ShowMessage(this, GetString(Resource.String.failed_to_access_database), MessageSeverity.Error);
|
||||
Finish();
|
||||
return;
|
||||
}
|
||||
|
||||
base.OnCreate(savedInstanceState);
|
||||
|
||||
if (LastNonConfigurationInstance != null)
|
||||
{
|
||||
@@ -329,11 +337,19 @@ namespace keepass2android
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
//we don't want any background thread to update/reload the database while we're in this activity.
|
||||
App.Kp2a.DatabasesBackgroundModificationLock.ExitReadLock();
|
||||
}
|
||||
|
||||
private bool hasRequestedKeyboardActivation = false;
|
||||
protected override void OnStart()
|
||||
{
|
||||
{
|
||||
|
||||
base.OnStart();
|
||||
if (PreferenceManager.GetDefaultSharedPreferences(this)
|
||||
.GetBoolean(GetString(Resource.String.UseKp2aKeyboardInKp2a_key), false)
|
||||
|
||||
@@ -14,8 +14,18 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
<TextView
|
||||
android:id="@+id/background_ops_message"
|
||||
android:layout_width="wrap_content"
|
||||
@@ -37,6 +47,19 @@
|
||||
android:textSize="12sp"
|
||||
android:text="" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/cancel_background"
|
||||
style="?attr/materialIconButtonStyle"
|
||||
app:icon="@drawable/baseline_close_24"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="6dip"
|
||||
android:layout_margin="6dip"
|
||||
android:layout_weight="0"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -1256,4 +1256,5 @@
|
||||
<string name="switch_keyboard_inside_kp2a_enabled">Note: You have enabled App - Security - Use built-in keyboard inside Keepass2Android. This can cause this window to show when you open the app or edit an entry.</string>
|
||||
<string name="switch_keyboard_on_search_enabled">Note: You have enabled App - Security - Password access - Keyboard switching - Switch keyboard. This can cause this window to show when you search for an entry from the browser.</string>
|
||||
<string name="user_interaction_required">User interaction required. Please open the app.</string>
|
||||
<string name="failed_to_access_database">Database is currently in use and cannot be accessed.</string>
|
||||
</resources>
|
||||
@@ -78,7 +78,15 @@ namespace keepass2android
|
||||
|
||||
protected override void OnCreate(Bundle savedInstanceState)
|
||||
{
|
||||
base.OnCreate(savedInstanceState);
|
||||
//we don't want any background thread to update/reload the database while we're in this activity.
|
||||
if (!App.Kp2a.DatabasesBackgroundModificationLock.TryEnterReadLock(TimeSpan.FromSeconds(5)))
|
||||
{
|
||||
App.Kp2a.ShowMessage(this, GetString(Resource.String.failed_to_access_database), MessageSeverity.Error);
|
||||
Finish();
|
||||
return;
|
||||
}
|
||||
|
||||
base.OnCreate(savedInstanceState);
|
||||
|
||||
//if user presses back to leave this activity:
|
||||
SetResult(Result.Canceled);
|
||||
@@ -288,5 +296,12 @@ namespace keepass2android
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
}}
|
||||
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
App.Kp2a.DatabasesBackgroundModificationLock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace keepass2android
|
||||
if (filestorage is CachingFileStorage)
|
||||
{
|
||||
|
||||
task = new SynchronizeCachedDatabase(App.Kp2a, onOperationFinishedHandler);
|
||||
task = new SynchronizeCachedDatabase(App.Kp2a, onOperationFinishedHandler, new BackgroundDatabaseModificationLocker(App.Kp2a));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -116,15 +116,12 @@ namespace keepass2android
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Main implementation of the IKp2aApp interface for usage in the real app.
|
||||
/// </summary>
|
||||
public class Kp2aApp: IKp2aApp, ICacheSupervisor
|
||||
{
|
||||
|
||||
|
||||
public void Lock(bool allowQuickUnlock = true, bool lockWasTriggeredByTimeout = false)
|
||||
{
|
||||
public void Lock(bool allowQuickUnlock = true, bool lockWasTriggeredByTimeout = false)
|
||||
{
|
||||
if (OpenDatabases.Any())
|
||||
{
|
||||
@@ -194,7 +191,9 @@ 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,
|
||||
IDatabaseModificationWatcher modificationWatcher)
|
||||
{
|
||||
|
||||
|
||||
@@ -215,39 +214,50 @@ namespace keepass2android
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
|
||||
if (!statusLogger.ContinueWork())
|
||||
{
|
||||
throw new Java.Lang.InterruptedException();
|
||||
}
|
||||
|
||||
_openAttempts.Add(ioConnectionInfo);
|
||||
var newDb = new Database(new DrawableFactory(), this);
|
||||
newDb.LoadData(this, ioConnectionInfo, memoryStream, compositeKey, statusLogger, databaseFormat);
|
||||
|
||||
modificationWatcher.BeforeModifyDatabases();
|
||||
|
||||
|
||||
|
||||
if ((_currentDatabase == null) || makeCurrent) _currentDatabase = newDb;
|
||||
|
||||
bool replacedOpenDatabase = false;
|
||||
for (int i = 0; i < _openDatabases.Count; i++)
|
||||
try
|
||||
{
|
||||
if (_openDatabases[i].Ioc.IsSameFileAs(ioConnectionInfo))
|
||||
|
||||
|
||||
|
||||
if ((_currentDatabase == null) || makeCurrent) _currentDatabase = newDb;
|
||||
|
||||
bool replacedOpenDatabase = false;
|
||||
for (int i = 0; i < _openDatabases.Count; i++)
|
||||
{
|
||||
if (_currentDatabase == _openDatabases[i])
|
||||
if (_openDatabases[i].Ioc.IsSameFileAs(ioConnectionInfo))
|
||||
{
|
||||
_currentDatabase = newDb;
|
||||
if (_currentDatabase == _openDatabases[i])
|
||||
{
|
||||
_currentDatabase = newDb;
|
||||
}
|
||||
|
||||
replacedOpenDatabase = true;
|
||||
_openDatabases[i] = newDb;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
replacedOpenDatabase = true;
|
||||
_openDatabases[i] = newDb;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!replacedOpenDatabase)
|
||||
if (!replacedOpenDatabase)
|
||||
{
|
||||
_openDatabases.Add(newDb);
|
||||
}
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
modificationWatcher.AfterModifyDatabases();
|
||||
}
|
||||
|
||||
|
||||
if (createBackup)
|
||||
@@ -302,9 +312,9 @@ namespace keepass2android
|
||||
return newDb;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public void CloseDatabase(Database db)
|
||||
public void CloseDatabase(Database db)
|
||||
{
|
||||
|
||||
if (!_openDatabases.Contains(db))
|
||||
@@ -745,6 +755,11 @@ namespace keepass2android
|
||||
EventHandler<DialogClickEventArgs> cancelHandler,
|
||||
EventHandler dismissHandler,string messageSuffix = "")
|
||||
{
|
||||
if (Java.Lang.Thread.Interrupted())
|
||||
{
|
||||
throw new Java.Lang.InterruptedException();
|
||||
}
|
||||
|
||||
_currentlyPendingYesNoCancelQuestion = new YesNoCancelQuestion()
|
||||
{
|
||||
TitleKey = titleKey,
|
||||
@@ -1472,10 +1487,38 @@ namespace keepass2android
|
||||
_currentlyPendingYesNoCancelQuestion?.TryShow(this, OnUserInputDialogClose, OnUserInputDialogClose);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the database is updated from a background operation, that operation needs to acquire a writer lock on this.
|
||||
/// </summary>
|
||||
/// Activities can acquire a reader lock if they want to make sure that no background operation is modifying the database while they are open.
|
||||
public ReaderWriterLockSlim DatabasesBackgroundModificationLock { get; } = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
|
||||
|
||||
public bool CancelBackgroundOperations()
|
||||
{
|
||||
if (!DatabasesBackgroundModificationLock.TryEnterReadLock(TimeSpan.FromSeconds(5)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
BackgroundOperationRunner.Instance.CancelAll();
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
DatabasesBackgroundModificationLock.ExitReadLock();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///Application class for Keepass2Android: Contains static Database variable to be used by all components.
|
||||
|
||||
///Application class for Keepass2Android: Contains static Database variable to be used by all components.
|
||||
#if NoNet
|
||||
[Application(Debuggable=false, Label=AppNames.AppName)]
|
||||
#else
|
||||
|
||||
@@ -48,7 +48,15 @@ namespace keepass2android.search
|
||||
{
|
||||
protected override void OnCreate (Bundle bundle)
|
||||
{
|
||||
base.OnCreate (bundle);
|
||||
//we don't want any background thread to update/reload the database while we're in this activity. We're showing a temporary group, so background updating doesn't work well.
|
||||
if (!App.Kp2a.DatabasesBackgroundModificationLock.TryEnterReadLock(TimeSpan.FromSeconds(5)))
|
||||
{
|
||||
App.Kp2a.ShowMessage(this, GetString(Resource.String.failed_to_access_database), MessageSeverity.Error);
|
||||
Finish();
|
||||
return;
|
||||
}
|
||||
|
||||
base.OnCreate (bundle);
|
||||
|
||||
if ( IsFinishing ) {
|
||||
return;
|
||||
@@ -59,7 +67,13 @@ namespace keepass2android.search
|
||||
ProcessIntent(Intent);
|
||||
}
|
||||
|
||||
public override bool EntriesBelongToCurrentDatabaseOnly
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
App.Kp2a.DatabasesBackgroundModificationLock.ExitReadLock();
|
||||
}
|
||||
|
||||
public override bool EntriesBelongToCurrentDatabaseOnly
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Runtime;
|
||||
using Android.Util;
|
||||
using Android.Views;
|
||||
|
||||
namespace keepass2android.views;
|
||||
|
||||
public class BackgroundOperationContainer : LinearLayout, IProgressUi
|
||||
{
|
||||
protected BackgroundOperationContainer(IntPtr javaReference, JniHandleOwnership transfer) : base(
|
||||
javaReference, transfer)
|
||||
{
|
||||
}
|
||||
|
||||
public BackgroundOperationContainer(Context context) : base(context)
|
||||
{
|
||||
}
|
||||
|
||||
public BackgroundOperationContainer(Context context, IAttributeSet attrs) : base(context, attrs)
|
||||
{
|
||||
Initialize(attrs);
|
||||
}
|
||||
|
||||
public BackgroundOperationContainer(Context context, IAttributeSet attrs, int defStyle) : base(context,
|
||||
attrs, defStyle)
|
||||
{
|
||||
Initialize(attrs);
|
||||
}
|
||||
|
||||
private void Initialize(IAttributeSet attrs)
|
||||
{
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater)Context.GetSystemService(Context.LayoutInflaterService);
|
||||
inflater.Inflate(Resource.Layout.background_operation_container, this);
|
||||
|
||||
FindViewById(Resource.Id.cancel_background).Click += (obj,args) =>
|
||||
{
|
||||
App.Kp2a.CancelBackgroundOperations();
|
||||
};
|
||||
}
|
||||
|
||||
public void Show()
|
||||
{
|
||||
new Handler(Looper.MainLooper).Post(() =>
|
||||
{
|
||||
Visibility = ViewStates.Visible;
|
||||
FindViewById<TextView>(Resource.Id.background_ops_message)!.Visibility = ViewStates.Gone;
|
||||
FindViewById<TextView>(Resource.Id.background_ops_submessage)!.Visibility = ViewStates.Gone;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
new Handler(Looper.MainLooper).Post(() =>
|
||||
{
|
||||
String activityType = Context.GetType().FullName;
|
||||
Kp2aLog.Log("Hiding background ops container in" + activityType);
|
||||
Visibility = ViewStates.Gone;
|
||||
});
|
||||
}
|
||||
|
||||
public void UpdateMessage(string message)
|
||||
{
|
||||
new Handler(Looper.MainLooper).Post(() =>
|
||||
{
|
||||
TextView messageTextView = FindViewById<TextView>(Resource.Id.background_ops_message)!;
|
||||
if (string.IsNullOrEmpty(message))
|
||||
{
|
||||
messageTextView.Visibility = ViewStates.Gone;
|
||||
}
|
||||
else
|
||||
{
|
||||
messageTextView.Visibility = ViewStates.Visible;
|
||||
messageTextView.Text = message;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void UpdateSubMessage(string submessage)
|
||||
{
|
||||
new Handler(Looper.MainLooper).Post(() =>
|
||||
{
|
||||
TextView subMessageTextView = FindViewById<TextView>(Resource.Id.background_ops_submessage)!;
|
||||
if (string.IsNullOrEmpty(submessage))
|
||||
{
|
||||
subMessageTextView.Visibility = ViewStates.Gone;
|
||||
}
|
||||
else
|
||||
{
|
||||
subMessageTextView.Visibility = ViewStates.Visible;
|
||||
subMessageTextView.Text = submessage;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ using Android.App;
|
||||
using Android.Content;
|
||||
using Android.Content.Res;
|
||||
using Android.Graphics;
|
||||
using Android.OS;
|
||||
using Android.Runtime;
|
||||
using Android.Text;
|
||||
using Android.Text.Method;
|
||||
@@ -104,92 +103,4 @@ namespace keepass2android.views
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class BackgroundOperationContainer : LinearLayout, IProgressUi
|
||||
{
|
||||
protected BackgroundOperationContainer(IntPtr javaReference, JniHandleOwnership transfer) : base(
|
||||
javaReference, transfer)
|
||||
{
|
||||
}
|
||||
|
||||
public BackgroundOperationContainer(Context context) : base(context)
|
||||
{
|
||||
}
|
||||
|
||||
public BackgroundOperationContainer(Context context, IAttributeSet attrs) : base(context, attrs)
|
||||
{
|
||||
Initialize(attrs);
|
||||
}
|
||||
|
||||
public BackgroundOperationContainer(Context context, IAttributeSet attrs, int defStyle) : base(context,
|
||||
attrs, defStyle)
|
||||
{
|
||||
Initialize(attrs);
|
||||
}
|
||||
|
||||
private void Initialize(IAttributeSet attrs)
|
||||
{
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater)Context.GetSystemService(Context.LayoutInflaterService);
|
||||
inflater.Inflate(Resource.Layout.background_operation_container, this);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void Show()
|
||||
{
|
||||
new Handler(Looper.MainLooper).Post(() =>
|
||||
{
|
||||
Visibility = ViewStates.Visible;
|
||||
FindViewById<TextView>(Resource.Id.background_ops_message)!.Visibility = ViewStates.Gone;
|
||||
FindViewById<TextView>(Resource.Id.background_ops_submessage)!.Visibility = ViewStates.Gone;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
new Handler(Looper.MainLooper).Post(() =>
|
||||
{
|
||||
String activityType = Context.GetType().FullName;
|
||||
Kp2aLog.Log("Hiding background ops container in" + activityType);
|
||||
Visibility = ViewStates.Gone;
|
||||
});
|
||||
}
|
||||
|
||||
public void UpdateMessage(string message)
|
||||
{
|
||||
new Handler(Looper.MainLooper).Post(() =>
|
||||
{
|
||||
TextView messageTextView = FindViewById<TextView>(Resource.Id.background_ops_message)!;
|
||||
if (string.IsNullOrEmpty(message))
|
||||
{
|
||||
messageTextView.Visibility = ViewStates.Gone;
|
||||
}
|
||||
else
|
||||
{
|
||||
messageTextView.Visibility = ViewStates.Visible;
|
||||
messageTextView.Text = message;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void UpdateSubMessage(string submessage)
|
||||
{
|
||||
new Handler(Looper.MainLooper).Post(() =>
|
||||
{
|
||||
TextView subMessageTextView = FindViewById<TextView>(Resource.Id.background_ops_submessage)!;
|
||||
if (string.IsNullOrEmpty(submessage))
|
||||
{
|
||||
subMessageTextView.Visibility = ViewStates.Gone;
|
||||
}
|
||||
else
|
||||
{
|
||||
subMessageTextView.Visibility = ViewStates.Visible;
|
||||
subMessageTextView.Text = submessage;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user