diff --git a/src/Kp2aBusinessLogic/BlockingOperationRunner.cs b/src/Kp2aBusinessLogic/BlockingOperationRunner.cs index fbe0d0d7..b35ff2b1 100644 --- a/src/Kp2aBusinessLogic/BlockingOperationRunner.cs +++ b/src/Kp2aBusinessLogic/BlockingOperationRunner.cs @@ -32,46 +32,59 @@ namespace keepass2android //for handling Activity recreation situations, we need access to the currently active task. It must hold that there is no more than one active task. private static BlockingOperationRunner _currentTask = null; - public static void SetNewActiveActivity(Activity activeActivity) + public static void SetNewActiveContext(Context activeContext) { if (_currentTask != null) { - _currentTask.ActiveActivity = activeActivity; + _currentTask.ActiveContext = activeContext; } } - public static void RemoveActiveActivity(Activity activity) + public static void RemoveActiveContext(Context context) { - if ((_currentTask != null) && (_currentTask._activeActivity == activity)) - _currentTask.ActiveActivity = null; + if ((_currentTask != null) && (_currentTask._activeContext == context)) + _currentTask.ActiveContext = null; } - public Context ActiveActivity + public Context ActiveContext { - get { return _activeActivity; } + get { return _activeContext; } private set { - _activeActivity = value; + _activeContext = value; - if (_activeActivity != null) + if (_activeContext is Activity) { SetupProgressDialog(_app); _progressDialog.Show(); } - } + else if (_activeContext is Service and IProgressUiProvider progressUiProvider) + { + _statusLogger?.SetNewProgressUi(progressUiProvider.ProgressUi); + } + + } } + public static bool HasActiveTask + { + get + { + return _currentTask != null; + } + } + private readonly Handler _handler; private readonly OperationWithFinishHandler _task; private IProgressDialog _progressDialog; private readonly IKp2aApp _app; private Java.Lang.Thread _thread; - private Context _activeActivity; - private ProgressDialogStatusLogger _progressDialogStatusLogger; + private Context _activeContext; + private ProgressUiAsStatusLoggerAdapter _statusLogger; public BlockingOperationRunner(IKp2aApp app, OperationWithFinishHandler task) { - _activeActivity = app.ActiveContext; + _activeContext = app.ActiveContext; _task = task; _handler = app.UiThreadHandler; _app = app; @@ -84,7 +97,7 @@ namespace keepass2android // Set code to run when this is finished _task.operationFinishedHandler = new AfterTask(app, task.operationFinishedHandler, _handler, this); - _task.SetStatusLogger(_progressDialogStatusLogger); + _task.SetStatusLogger(_statusLogger); } @@ -94,12 +107,12 @@ namespace keepass2android string currentMessage = "Initializing..."; string currentSubmessage = ""; - if (_progressDialogStatusLogger != null) + if (_statusLogger != null) { - currentMessage = _progressDialogStatusLogger.Message; - currentSubmessage = _progressDialogStatusLogger.SubMessage; + currentMessage = _statusLogger.LastMessage; + currentSubmessage = _statusLogger.LastSubMessage; } - + if (_progressDialog != null) { var pd = _progressDialog; @@ -110,11 +123,21 @@ namespace keepass2android } // Show process dialog - _progressDialog = app.CreateProgressDialog(_activeActivity); - _progressDialog.SetTitle(_app.GetResourceString(UiStringKey.progress_title)); - _progressDialogStatusLogger = new ProgressDialogStatusLogger(_app, _handler, _progressDialog); - _progressDialogStatusLogger.UpdateMessage(currentMessage); - _progressDialogStatusLogger.UpdateSubMessage(currentSubmessage); + _progressDialog = app.CreateProgressDialog(_activeContext); + + var progressUi = new ProgressDialogUi(_app, app.UiThreadHandler, _progressDialog); + if (_statusLogger == null) + { + _statusLogger = new ProgressUiAsStatusLoggerAdapter(progressUi, app); + } + else + { + _statusLogger.SetNewProgressUi(progressUi); + } + + _statusLogger.StartLogging("", false); + _statusLogger.UpdateMessage(currentMessage); + _statusLogger.UpdateSubMessage(currentSubmessage); } public void Run(bool allowOverwriteCurrentTask = false) diff --git a/src/Kp2aBusinessLogic/IKp2aApp.cs b/src/Kp2aBusinessLogic/IKp2aApp.cs index 984ab2de..2226f6e7 100644 --- a/src/Kp2aBusinessLogic/IKp2aApp.cs +++ b/src/Kp2aBusinessLogic/IKp2aApp.cs @@ -143,5 +143,10 @@ namespace keepass2android ReaderWriterLockSlim DatabasesBackgroundModificationLock { get; } bool CancelBackgroundOperations(); + + /// + /// Registers an action that should be executed when the context instance with the given id has been resumed. + /// + void RegisterPendingActionForContextInstance(int contextInstanceId, ActionOnOperationFinished actionToPerformWhenContextIsResumed); } } \ No newline at end of file diff --git a/src/Kp2aBusinessLogic/ProgressDialogStatusLogger.cs b/src/Kp2aBusinessLogic/ProgressDialogStatusLogger.cs index 280a1795..5279a758 100644 --- a/src/Kp2aBusinessLogic/ProgressDialogStatusLogger.cs +++ b/src/Kp2aBusinessLogic/ProgressDialogStatusLogger.cs @@ -25,6 +25,8 @@ namespace keepass2android public interface IKp2aStatusLogger : IStatusLogger { void UpdateMessage(UiStringKey stringKey); + string LastMessage { get; } + string LastSubMessage { get; } } public interface IProgressUi @@ -62,14 +64,16 @@ namespace keepass2android return true; } + private string _lastMessage; + private string _lastSubMessage; public void UpdateMessage(string message) { - + _lastMessage = message; } public void UpdateSubMessage(string submessage) { - + _lastSubMessage = submessage; } public bool ContinueWork() @@ -81,133 +85,88 @@ namespace keepass2android { } - } - - public abstract class Kp2aAppStatusLogger : IKp2aStatusLogger - { - protected IKp2aApp _app; - - public Kp2aAppStatusLogger(IKp2aApp app) - { - _app = app; - } - - #region IStatusLogger implementation - - public void StartLogging(string strOperation, bool bWriteOperationToLog) - { - - } - - public void EndLogging() - { - - } - - public bool SetProgress(uint uPercent) - { - return true; - } - - public bool SetText(string strNewText, LogStatusType lsType) - { - if (strNewText.StartsWith("KP2AKEY_")) - { - UiStringKey key; - if (Enum.TryParse(strNewText.Substring("KP2AKEY_".Length), true, out key)) - { - UpdateMessage(_app.GetResourceString(key), lsType); - return true; - } - } - UpdateMessage(strNewText, lsType); - - return true; - } - - public abstract void UpdateMessage(string message); - public abstract void UpdateSubMessage(string submessage); - - private void UpdateMessage(string message, LogStatusType lsType) - { - if (lsType == LogStatusType.AdditionalInfo) - { - UpdateSubMessage(message); - } - else - { - UpdateMessage(message); - } - } - - public bool ContinueWork() - { - return true; - } - - #endregion - - public void UpdateMessage(UiStringKey stringKey) - { - if (_app != null) - UpdateMessage(_app.GetResourceString(stringKey)); - } + public string LastMessage { get { return _lastMessage; } } + public string LastSubMessage { get { return _lastSubMessage; } } } /// /// StatusLogger implementation which shows the progress in a progress dialog /// - public class ProgressDialogStatusLogger: Kp2aAppStatusLogger + public class ProgressDialogUi: IProgressUi { private readonly IProgressDialog _progressDialog; private readonly Handler _handler; private string _message = ""; private string _submessage; + private readonly IKp2aApp _app; - public String SubMessage => _submessage; - public String Message => _message; + public String LastSubMessage => _submessage; + public String LastMessage => _message; - public ProgressDialogStatusLogger(IKp2aApp app, Handler handler, IProgressDialog pd) - : base(app){ + public ProgressDialogUi(IKp2aApp app, Handler handler, IProgressDialog pd) + { + _app = app; _progressDialog = pd; _handler = handler; } - - - public override void UpdateMessage (String message) - { - Kp2aLog.Log("status message: " + message); + + public void UpdateSubMessage(String submessage) + { + Kp2aLog.Log("status submessage: " + submessage); + _submessage = submessage; + if (_app != null && _progressDialog != null && _handler != null) + { + _handler.Post(() => + { + if (!String.IsNullOrEmpty(submessage)) + { + _progressDialog.SetMessage(_message + " (" + submessage + ")"); + } + else + { + _progressDialog.SetMessage(_message); + } + } + ); + } + } + + public void Show() + { + _handler.Post(() => + { + + _progressDialog?.Show(); + + }); + } + + public void Hide() + { + _handler.Post(() => + { + + _progressDialog?.Dismiss(); + + }); + } + + public void UpdateMessage(string message) + { + Kp2aLog.Log("status message: " + message); _message = message; - if ( _app!= null && _progressDialog != null && _handler != null ) { - _handler.Post(() => {_progressDialog.SetMessage(message); } ); - } - } + if (_app != null && _progressDialog != null && _handler != null) + { + _handler.Post(() => { _progressDialog.SetMessage(message); }); + } + } - public override void UpdateSubMessage(String submessage) - { - Kp2aLog.Log("status submessage: " + submessage); - _submessage = submessage; - if (_app != null && _progressDialog != null && _handler != null) - { - _handler.Post(() => - { - if (!String.IsNullOrEmpty(submessage)) - { - _progressDialog.SetMessage(_message + " (" + submessage + ")"); - } - else - { - _progressDialog.SetMessage(_message); - } - } - ); - } - } + } - } + } diff --git a/src/Kp2aBusinessLogic/ProgressUiAsStatusLoggerAdapter.cs b/src/Kp2aBusinessLogic/ProgressUiAsStatusLoggerAdapter.cs index de444374..0b58f6e2 100644 --- a/src/Kp2aBusinessLogic/ProgressUiAsStatusLoggerAdapter.cs +++ b/src/Kp2aBusinessLogic/ProgressUiAsStatusLoggerAdapter.cs @@ -87,4 +87,7 @@ public class ProgressUiAsStatusLoggerAdapter : IKp2aStatusLogger if (_app != null) UpdateMessage(_app.GetResourceString(stringKey)); } + + public string LastMessage { get { return _lastMessage; } } + public string LastSubMessage { get { return _lastSubMessage; } } } \ No newline at end of file diff --git a/src/Kp2aBusinessLogic/database/edit/ActionOnOperationFinished.cs b/src/Kp2aBusinessLogic/database/edit/ActionOnOperationFinished.cs index c17c36de..fd407d35 100644 --- a/src/Kp2aBusinessLogic/database/edit/ActionOnOperationFinished.cs +++ b/src/Kp2aBusinessLogic/database/edit/ActionOnOperationFinished.cs @@ -19,6 +19,7 @@ using System; using Android.App; using Android.Content; using Android.OS; +using keepass2android; namespace keepass2android { @@ -53,3 +54,30 @@ namespace keepass2android } } + public class ActionInContextInstanceOnOperationFinished : ActionOnOperationFinished + { + private readonly int _contextInstanceId; + private IKp2aApp _app; + + public ActionInContextInstanceOnOperationFinished(int contextInstanceId, IKp2aApp app, ActionToPerformOnFinsh actionToPerform) : base(app, actionToPerform) + { + _contextInstanceId = contextInstanceId; + _app = app; + } + public ActionInContextInstanceOnOperationFinished(int contextInstanceId, IKp2aApp app, ActionToPerformOnFinsh actionToPerform, OnOperationFinishedHandler operationFinishedHandler) : base(app, actionToPerform, operationFinishedHandler) + { + _contextInstanceId = contextInstanceId; + _app = app; + } + + public override void Run() + { + if ((ActiveContext as IContextInstanceIdProvider)?.ContextInstanceId != _contextInstanceId) + { + _app.RegisterPendingActionForContextInstance(_contextInstanceId, this); + } + else base.Run(); + } + + } + diff --git a/src/Kp2aBusinessLogic/database/edit/IContextInstanceIdProvider.cs b/src/Kp2aBusinessLogic/database/edit/IContextInstanceIdProvider.cs new file mode 100644 index 00000000..ac1d8631 --- /dev/null +++ b/src/Kp2aBusinessLogic/database/edit/IContextInstanceIdProvider.cs @@ -0,0 +1,13 @@ +namespace keepass2android; + +// A context instance can be the instance of an Activity. Even if the activity is recreated (due to a configuration change, for example), the instance id must remain the same +// but it must be different for other activities/services or if the activity is finished and then starts again. +// We want to be able to perform actions on a context instance, even though that instance might not live at the time when we want to perform the action. +// In that case, we want to be able to register the action such that it is performed when the activity is recreated. +public interface IContextInstanceIdProvider +{ + + int ContextInstanceId { get; } + + +} \ No newline at end of file diff --git a/src/keepass2android-app/EntryEditActivity.cs b/src/keepass2android-app/EntryEditActivity.cs index b2d6b769..e293e0bd 100644 --- a/src/keepass2android-app/EntryEditActivity.cs +++ b/src/keepass2android-app/EntryEditActivity.cs @@ -551,9 +551,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 - ActionOnOperationFinished afterAddEntry = new ActionOnOperationFinished(App.Kp2a, (success, message, activity) => + ActionOnOperationFinished afterAddEntry = new ActionInContextInstanceOnOperationFinished(ContextInstanceId, App.Kp2a, (success, message, context) => { - if (success && activity is EntryEditActivity entryEditActivity) + if (success && context is EntryEditActivity entryEditActivity) AppTask.AfterAddNewEntry(entryEditActivity, newEntry); },closeOrShowError); diff --git a/src/keepass2android-app/LifecycleAwareActivity.cs b/src/keepass2android-app/LifecycleAwareActivity.cs index 68b75b57..97931c9c 100644 --- a/src/keepass2android-app/LifecycleAwareActivity.cs +++ b/src/keepass2android-app/LifecycleAwareActivity.cs @@ -25,7 +25,7 @@ using Android.Runtime; namespace keepass2android { - public abstract class LifecycleAwareActivity : AndroidX.AppCompat.App.AppCompatActivity + public abstract class LifecycleAwareActivity : AndroidX.AppCompat.App.AppCompatActivity, IContextInstanceIdProvider { protected override void AttachBaseContext(Context baseContext) { @@ -84,12 +84,11 @@ namespace keepass2android return baseRes; } - public Action? OnResumeListener { get; set; } protected override void OnResume() { base.OnResume(); - OnResumeListener?.Invoke(); + App.Kp2a.PerformPendingActions(_instanceId); Kp2aLog.Log(ClassName + ".OnResume " + ID); if (App.Kp2a.CurrentDb == null) @@ -105,28 +104,40 @@ namespace keepass2android protected override void OnStart() { App.Kp2a.ActiveContext = this; - BlockingOperationRunner.SetNewActiveActivity(this); + BlockingOperationRunner.SetNewActiveContext(this); BackgroundOperationRunner.Instance.SetNewActiveContext( App.Kp2a); base.OnStart(); Kp2aLog.Log(ClassName + ".OnStart" + " " + ID); } + const string ID_KEY = "kp2a_context_instance_id"; + const int InvalidId = -1; + + private int _instanceId; + protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); + + _instanceId = bundle?.GetInt(ID_KEY, InvalidId) ?? InvalidId; + if (_instanceId == InvalidId) + { + _instanceId = _nextContextInstanceId++; + } OnCreateListener?.Invoke(bundle); - Kp2aLog.Log(ClassName + ".OnCreate" + " " + ID); + Kp2aLog.Log(ClassName + ".OnCreate" + " " + ID + " (instance=" + _instanceId +")"); Kp2aLog.Log(ClassName + ":apptask=" + Intent.GetStringExtra("KP2A_APP_TASK_TYPE") + " " + ID); } + protected override void OnDestroy() { base.OnDestroy(); - Kp2aLog.Log(ClassName + ".OnDestroy" + IsFinishing.ToString() + " " + ID); + Kp2aLog.Log(ClassName + ".OnDestroy " + IsFinishing.ToString() + " " + ID); } protected override void OnPause() @@ -139,14 +150,19 @@ namespace keepass2android { base.OnStop(); Kp2aLog.Log(ClassName + ".OnStop" + " " + ID); - BlockingOperationRunner.RemoveActiveActivity(this); + BlockingOperationRunner.RemoveActiveContext(this); } protected override void OnSaveInstanceState(Bundle outState) { base.OnSaveInstanceState(outState); + outState.PutInt(ID_KEY, _instanceId); OnSaveInstanceStateListener?.Invoke(outState); } + + static int _nextContextInstanceId = 0; + + public int ContextInstanceId => _instanceId; } } diff --git a/src/keepass2android-app/app/App.cs b/src/keepass2android-app/app/App.cs index 07bc17bd..ebfb4bbf 100644 --- a/src/keepass2android-app/app/App.cs +++ b/src/keepass2android-app/app/App.cs @@ -522,7 +522,7 @@ namespace keepass2android private bool _isShowingUserInputDialog = false; private IMessagePresenter? _messagePresenter; private YesNoCancelQuestion? _currentlyPendingYesNoCancelQuestion = null; - private Context _activeContext; + private Context? _activeContext; private void AskForReload(Activity activity, Action actionOnResult) { @@ -882,8 +882,11 @@ namespace keepass2android public IProgressDialog CreateProgressDialog(Context ctx) { - return new RealProgressDialog(ctx, this); - } + var pd = new RealProgressDialog(ctx, this); + pd.SetTitle(GetResourceString(UiStringKey.progress_title)); + return pd; + + } public IFileStorage GetFileStorage(IOConnectionInfo iocInfo) { @@ -1480,7 +1483,7 @@ namespace keepass2android public Context ActiveContext { - get => _activeContext; + get => _activeContext ?? Application.Context; set { _activeContext = value; @@ -1513,6 +1516,47 @@ namespace keepass2android return true; + } + + private readonly Dictionary> _pendingActionsForContextInstances = new(); + private readonly object _pendingActionsForContextInstancesLock = new(); + public void RegisterPendingActionForContextInstance(int contextInstanceId, + ActionOnOperationFinished actionToPerformWhenContextIsResumed) + { + lock (_pendingActionsForContextInstancesLock) + { + + if (!_pendingActionsForContextInstances.TryGetValue(contextInstanceId, out var actions)) + { + actions = new List(); + _pendingActionsForContextInstances[contextInstanceId] = actions; + } + + actions.Add(actionToPerformWhenContextIsResumed); + } + } + + public void PerformPendingActions(int instanceId) + { + lock (_pendingActionsForContextInstancesLock) + { + if (_pendingActionsForContextInstances.TryGetValue(instanceId, out var actions)) + { + foreach (var action in actions) + { + try + { + action.Run(); + } + catch (Exception e) + { + Kp2aLog.LogUnexpectedError(e); + } + } + + _pendingActionsForContextInstances.Remove(instanceId); + } + } } }