change blocking operations to use BackgroundSyncService also. Introduce pending actions for context instances.

This commit is contained in:
Philipp Crocoll
2025-05-27 11:57:58 +02:00
parent 000d1254ec
commit 69f79c1b20
9 changed files with 235 additions and 144 deletions

View File

@@ -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. //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; private static BlockingOperationRunner _currentTask = null;
public static void SetNewActiveActivity(Activity activeActivity) public static void SetNewActiveContext(Context activeContext)
{ {
if (_currentTask != null) 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)) if ((_currentTask != null) && (_currentTask._activeContext == context))
_currentTask.ActiveActivity = null; _currentTask.ActiveContext = null;
} }
public Context ActiveActivity public Context ActiveContext
{ {
get { return _activeActivity; } get { return _activeContext; }
private set private set
{ {
_activeActivity = value; _activeContext = value;
if (_activeActivity != null) if (_activeContext is Activity)
{ {
SetupProgressDialog(_app); SetupProgressDialog(_app);
_progressDialog.Show(); _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 Handler _handler;
private readonly OperationWithFinishHandler _task; private readonly OperationWithFinishHandler _task;
private IProgressDialog _progressDialog; private IProgressDialog _progressDialog;
private readonly IKp2aApp _app; private readonly IKp2aApp _app;
private Java.Lang.Thread _thread; private Java.Lang.Thread _thread;
private Context _activeActivity; private Context _activeContext;
private ProgressDialogStatusLogger _progressDialogStatusLogger; private ProgressUiAsStatusLoggerAdapter _statusLogger;
public BlockingOperationRunner(IKp2aApp app, OperationWithFinishHandler task) public BlockingOperationRunner(IKp2aApp app, OperationWithFinishHandler task)
{ {
_activeActivity = app.ActiveContext; _activeContext = app.ActiveContext;
_task = task; _task = task;
_handler = app.UiThreadHandler; _handler = app.UiThreadHandler;
_app = app; _app = app;
@@ -84,7 +97,7 @@ namespace keepass2android
// Set code to run when this is finished // Set code to run when this is finished
_task.operationFinishedHandler = new AfterTask(app, task.operationFinishedHandler, _handler, this); _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 currentMessage = "Initializing...";
string currentSubmessage = ""; string currentSubmessage = "";
if (_progressDialogStatusLogger != null) if (_statusLogger != null)
{ {
currentMessage = _progressDialogStatusLogger.Message; currentMessage = _statusLogger.LastMessage;
currentSubmessage = _progressDialogStatusLogger.SubMessage; currentSubmessage = _statusLogger.LastSubMessage;
} }
if (_progressDialog != null) if (_progressDialog != null)
{ {
var pd = _progressDialog; var pd = _progressDialog;
@@ -110,11 +123,21 @@ namespace keepass2android
} }
// Show process dialog // Show process dialog
_progressDialog = app.CreateProgressDialog(_activeActivity); _progressDialog = app.CreateProgressDialog(_activeContext);
_progressDialog.SetTitle(_app.GetResourceString(UiStringKey.progress_title));
_progressDialogStatusLogger = new ProgressDialogStatusLogger(_app, _handler, _progressDialog); var progressUi = new ProgressDialogUi(_app, app.UiThreadHandler, _progressDialog);
_progressDialogStatusLogger.UpdateMessage(currentMessage); if (_statusLogger == null)
_progressDialogStatusLogger.UpdateSubMessage(currentSubmessage); {
_statusLogger = new ProgressUiAsStatusLoggerAdapter(progressUi, app);
}
else
{
_statusLogger.SetNewProgressUi(progressUi);
}
_statusLogger.StartLogging("", false);
_statusLogger.UpdateMessage(currentMessage);
_statusLogger.UpdateSubMessage(currentSubmessage);
} }
public void Run(bool allowOverwriteCurrentTask = false) public void Run(bool allowOverwriteCurrentTask = false)

View File

@@ -143,5 +143,10 @@ namespace keepass2android
ReaderWriterLockSlim DatabasesBackgroundModificationLock { get; } ReaderWriterLockSlim DatabasesBackgroundModificationLock { get; }
bool CancelBackgroundOperations(); bool CancelBackgroundOperations();
/// <summary>
/// Registers an action that should be executed when the context instance with the given id has been resumed.
/// </summary>
void RegisterPendingActionForContextInstance(int contextInstanceId, ActionOnOperationFinished actionToPerformWhenContextIsResumed);
} }
} }

View File

@@ -25,6 +25,8 @@ namespace keepass2android
public interface IKp2aStatusLogger : IStatusLogger public interface IKp2aStatusLogger : IStatusLogger
{ {
void UpdateMessage(UiStringKey stringKey); void UpdateMessage(UiStringKey stringKey);
string LastMessage { get; }
string LastSubMessage { get; }
} }
public interface IProgressUi public interface IProgressUi
@@ -62,14 +64,16 @@ namespace keepass2android
return true; return true;
} }
private string _lastMessage;
private string _lastSubMessage;
public void UpdateMessage(string message) public void UpdateMessage(string message)
{ {
_lastMessage = message;
} }
public void UpdateSubMessage(string submessage) public void UpdateSubMessage(string submessage)
{ {
_lastSubMessage = submessage;
} }
public bool ContinueWork() 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; } }
} }
/// <summary> /// <summary>
/// StatusLogger implementation which shows the progress in a progress dialog /// StatusLogger implementation which shows the progress in a progress dialog
/// </summary> /// </summary>
public class ProgressDialogStatusLogger: Kp2aAppStatusLogger public class ProgressDialogUi: IProgressUi
{ {
private readonly IProgressDialog _progressDialog; private readonly IProgressDialog _progressDialog;
private readonly Handler _handler; private readonly Handler _handler;
private string _message = ""; private string _message = "";
private string _submessage; private string _submessage;
private readonly IKp2aApp _app;
public String SubMessage => _submessage; public String LastSubMessage => _submessage;
public String Message => _message; public String LastMessage => _message;
public ProgressDialogStatusLogger(IKp2aApp app, Handler handler, IProgressDialog pd) public ProgressDialogUi(IKp2aApp app, Handler handler, IProgressDialog pd)
: base(app){ {
_app = app;
_progressDialog = pd; _progressDialog = pd;
_handler = handler; _handler = handler;
} }
public void UpdateSubMessage(String submessage)
public override void UpdateMessage (String message) {
{ Kp2aLog.Log("status submessage: " + submessage);
Kp2aLog.Log("status message: " + message); _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; _message = message;
if ( _app!= null && _progressDialog != null && _handler != null ) { if (_app != null && _progressDialog != null && _handler != null)
_handler.Post(() => {_progressDialog.SetMessage(message); } ); {
} _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);
}
}
);
}
}
}
} }

View File

@@ -87,4 +87,7 @@ public class ProgressUiAsStatusLoggerAdapter : IKp2aStatusLogger
if (_app != null) if (_app != null)
UpdateMessage(_app.GetResourceString(stringKey)); UpdateMessage(_app.GetResourceString(stringKey));
} }
public string LastMessage { get { return _lastMessage; } }
public string LastSubMessage { get { return _lastSubMessage; } }
} }

View File

@@ -19,6 +19,7 @@ using System;
using Android.App; using Android.App;
using Android.Content; using Android.Content;
using Android.OS; using Android.OS;
using keepass2android;
namespace 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();
}
}

View File

@@ -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; }
}

View File

@@ -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 //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); AppTask.AfterAddNewEntry(entryEditActivity, newEntry);
},closeOrShowError); },closeOrShowError);

View File

@@ -25,7 +25,7 @@ using Android.Runtime;
namespace keepass2android 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) protected override void AttachBaseContext(Context baseContext)
{ {
@@ -84,12 +84,11 @@ namespace keepass2android
return baseRes; return baseRes;
} }
public Action? OnResumeListener { get; set; }
protected override void OnResume() protected override void OnResume()
{ {
base.OnResume(); base.OnResume();
OnResumeListener?.Invoke(); App.Kp2a.PerformPendingActions(_instanceId);
Kp2aLog.Log(ClassName + ".OnResume " + ID); Kp2aLog.Log(ClassName + ".OnResume " + ID);
if (App.Kp2a.CurrentDb == null) if (App.Kp2a.CurrentDb == null)
@@ -105,28 +104,40 @@ namespace keepass2android
protected override void OnStart() protected override void OnStart()
{ {
App.Kp2a.ActiveContext = this; App.Kp2a.ActiveContext = this;
BlockingOperationRunner.SetNewActiveActivity(this); BlockingOperationRunner.SetNewActiveContext(this);
BackgroundOperationRunner.Instance.SetNewActiveContext( App.Kp2a); BackgroundOperationRunner.Instance.SetNewActiveContext( App.Kp2a);
base.OnStart(); base.OnStart();
Kp2aLog.Log(ClassName + ".OnStart" + " " + ID); 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) protected override void OnCreate(Bundle bundle)
{ {
base.OnCreate(bundle); base.OnCreate(bundle);
_instanceId = bundle?.GetInt(ID_KEY, InvalidId) ?? InvalidId;
if (_instanceId == InvalidId)
{
_instanceId = _nextContextInstanceId++;
}
OnCreateListener?.Invoke(bundle); 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); Kp2aLog.Log(ClassName + ":apptask=" + Intent.GetStringExtra("KP2A_APP_TASK_TYPE") + " " + ID);
} }
protected override void OnDestroy() protected override void OnDestroy()
{ {
base.OnDestroy(); base.OnDestroy();
Kp2aLog.Log(ClassName + ".OnDestroy" + IsFinishing.ToString() + " " + ID); Kp2aLog.Log(ClassName + ".OnDestroy " + IsFinishing.ToString() + " " + ID);
} }
protected override void OnPause() protected override void OnPause()
@@ -139,14 +150,19 @@ namespace keepass2android
{ {
base.OnStop(); base.OnStop();
Kp2aLog.Log(ClassName + ".OnStop" + " " + ID); Kp2aLog.Log(ClassName + ".OnStop" + " " + ID);
BlockingOperationRunner.RemoveActiveActivity(this); BlockingOperationRunner.RemoveActiveContext(this);
} }
protected override void OnSaveInstanceState(Bundle outState) protected override void OnSaveInstanceState(Bundle outState)
{ {
base.OnSaveInstanceState(outState); base.OnSaveInstanceState(outState);
outState.PutInt(ID_KEY, _instanceId);
OnSaveInstanceStateListener?.Invoke(outState); OnSaveInstanceStateListener?.Invoke(outState);
} }
static int _nextContextInstanceId = 0;
public int ContextInstanceId => _instanceId;
} }
} }

View File

@@ -522,7 +522,7 @@ namespace keepass2android
private bool _isShowingUserInputDialog = false; private bool _isShowingUserInputDialog = false;
private IMessagePresenter? _messagePresenter; private IMessagePresenter? _messagePresenter;
private YesNoCancelQuestion? _currentlyPendingYesNoCancelQuestion = null; private YesNoCancelQuestion? _currentlyPendingYesNoCancelQuestion = null;
private Context _activeContext; private Context? _activeContext;
private void AskForReload(Activity activity, Action<bool> actionOnResult) private void AskForReload(Activity activity, Action<bool> actionOnResult)
{ {
@@ -882,8 +882,11 @@ namespace keepass2android
public IProgressDialog CreateProgressDialog(Context ctx) 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) public IFileStorage GetFileStorage(IOConnectionInfo iocInfo)
{ {
@@ -1480,7 +1483,7 @@ namespace keepass2android
public Context ActiveContext public Context ActiveContext
{ {
get => _activeContext; get => _activeContext ?? Application.Context;
set set
{ {
_activeContext = value; _activeContext = value;
@@ -1513,6 +1516,47 @@ namespace keepass2android
return true; return true;
}
private readonly Dictionary<int, List<ActionOnOperationFinished>> _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<ActionOnOperationFinished>();
_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);
}
}
} }
} }