fix issues with background operations implementation, added more logging

This commit is contained in:
Philipp Crocoll
2025-06-03 14:44:01 +02:00
parent 6351e1f3d0
commit a4a3112dc6
14 changed files with 326 additions and 214 deletions

View File

@@ -21,6 +21,7 @@ using System.IO;
using Android; using Android;
using Android.App; using Android.App;
using Android.Content; using Android.Content;
using Android.OS;
using Android.Preferences; using Android.Preferences;
using KeePassLib.Serialization; using KeePassLib.Serialization;
@@ -34,9 +35,17 @@ namespace keepass2android
public static void Log(string message) public static void Log(string message)
{ {
if (message != null) if (message != null)
Android.Util.Log.Debug("KP2A", message); {
if (LogToFile) message += Thread.CurrentThread.ManagedThreadId != 0
? " (Thread ID: " + Thread.CurrentThread.ManagedThreadId + ")"
: "";
if (Looper.MainLooper == Looper.MyLooper())
message += " (Main Looper)";
Android.Util.Log.Debug("KP2A", message);
}
if (LogToFile)
{ {
lock (_fileLocker) lock (_fileLocker)
{ {

View File

@@ -208,7 +208,7 @@ namespace KeePassLib.Serialization
if (!string.IsNullOrEmpty(strHash) && (m_pbHashOfHeader != null) && if (!string.IsNullOrEmpty(strHash) && (m_pbHashOfHeader != null) &&
!m_bRepairMode) !m_bRepairMode)
{ {
Debug.Assert(m_uFileVersion < FileVersion32_4); // Debug.Assert(m_uFileVersion < FileVersion32_4);
byte[] pbHash = Convert.FromBase64String(strHash); byte[] pbHash = Convert.FromBase64String(strHash);
if (!MemUtil.ArraysEqual(pbHash, m_pbHashOfHeader)) if (!MemUtil.ArraysEqual(pbHash, m_pbHashOfHeader))

View File

@@ -488,7 +488,7 @@ namespace KeePassLib.Serialization
ProtectedBinary pb = new ProtectedBinary(bProt, pbData, ProtectedBinary pb = new ProtectedBinary(bProt, pbData,
1, pbData.Length - 1); 1, pbData.Length - 1);
Debug.Assert(m_pbsBinaries.Find(pb) < 0); // No deduplication? //Debug.Assert(m_pbsBinaries.Find(pb) < 0); // No deduplication?
m_pbsBinaries.Add(pb); m_pbsBinaries.Add(pb);
if (bProt) MemUtil.ZeroByteArray(pbData); if (bProt) MemUtil.ZeroByteArray(pbData);

View File

@@ -1,6 +1,7 @@
using Android.App; using Android.App;
using Android.Content; using Android.Content;
using Android.OS; using Android.OS;
using System.Threading.Tasks;
using Thread = Java.Lang.Thread; using Thread = Java.Lang.Thread;
namespace keepass2android; namespace keepass2android;
@@ -59,10 +60,11 @@ public class OperationRunner
public void Run(IKp2aApp app, OperationWithFinishHandler operation, bool runBlocking = false) public void Run(IKp2aApp app, OperationWithFinishHandler operation, bool runBlocking = false)
{ {
Kp2aLog.Log("OPR: Run: " + operation.GetType().Name + ", runBlocking: " + runBlocking);
lock (Instance._taskQueueLock) lock (Instance._taskQueueLock)
{ {
_taskQueue.Enqueue(new OperationWithMetadata(){ Operation = operation, RunBlocking = runBlocking}); _taskQueue.Enqueue(new OperationWithMetadata(){ Operation = operation, RunBlocking = runBlocking});
SetNewActiveContext(app); operation.SetStatusLogger(_statusLogger);
// Start thread to run the task (unless it's already running) // Start thread to run the task (unless it's already running)
if (_thread == null) if (_thread == null)
@@ -79,6 +81,7 @@ public class OperationRunner
{ {
_thread = null; _thread = null;
_statusLogger.EndLogging(); _statusLogger.EndLogging();
Kp2aLog.Log("OPR: task queue empty. Stopping operation runner thread.");
break; break;
} }
else else
@@ -89,21 +92,38 @@ public class OperationRunner
if (_currentlyRunningTask.Value.RunBlocking) if (_currentlyRunningTask.Value.RunBlocking)
{ {
app.UiThreadHandler.Post(() => TrySetupProgressDialog()); Kp2aLog.Log("OPR: Run. Posting to set up progress dialog for blocking task: " + _currentlyRunningTask?.Operation?.GetType()?.Name ?? "null");
app.UiThreadHandler.Post(
() =>
{
Kp2aLog.Log("OPR: Run. Starting Setting up progress dialog for blocking task: " + _currentlyRunningTask?.Operation?.GetType()?.Name ?? "null");
TrySetupProgressDialog();
Kp2aLog.Log("OPR: Run. Finished Setting up progress dialog for blocking task: " + _currentlyRunningTask?.Operation?.GetType()?.Name ?? "null");
});
} }
var originalFinishedHandler = _currentlyRunningTask.Value.Operation.operationFinishedHandler; var originalFinishedHandler = _currentlyRunningTask.Value.Operation.operationFinishedHandler;
_currentlyRunningTask.Value.Operation.operationFinishedHandler = new ActionOnOperationFinished(app, ( _currentlyRunningTask.Value.Operation.operationFinishedHandler = new ActionOnOperationFinished(app, (
(success, message, context) => (success, message, context) =>
{ {
if (_currentlyRunningTask.Value.RunBlocking) if (_currentlyRunningTask?.RunBlocking == true)
{ {
_progressDialog?.Dismiss(); Kp2aLog.Log("OPR: Run. Blocking task finished: " + _currentlyRunningTask?.Operation?.GetType()?.Name ?? "null");
_app.UiThreadHandler.Post(() =>
{
Kp2aLog.Log("OPR: Starting Dismissing progress dialog");
_progressDialog?.Dismiss();
Kp2aLog.Log("OPR: Finished Dismissing progress dialog");
}
);
} }
Kp2aLog.Log("OPR: Run. Finished handler called for task: " + _currentlyRunningTask?.Operation?.GetType()?.Name ?? "null");
_currentlyRunningTask = null; _currentlyRunningTask = null;
}), originalFinishedHandler); }), originalFinishedHandler);
Kp2aLog.Log("OPR: starting to run " + _currentlyRunningTask?.Operation?.GetType()?.Name ?? "null");
_currentlyRunningTask.Value.Operation.Run(); _currentlyRunningTask.Value.Operation.Run();
while (_currentlyRunningTask != null) while (_currentlyRunningTask != null)
{ {
try try
@@ -115,12 +135,15 @@ public class OperationRunner
Kp2aLog.Log("Thread interrupted."); Kp2aLog.Log("Thread interrupted.");
} }
} }
Kp2aLog.Log("OPR: waiting for next task in queue...");
} }
}); });
_thread.Start(); _thread.Start();
} }
else Kp2aLog.Log("OPR: thread already running, only enqueued " + operation.GetType().Name );
} }
} }
@@ -128,6 +151,7 @@ public class OperationRunner
private bool TrySetupProgressDialog() private bool TrySetupProgressDialog()
{ {
Kp2aLog.Log("OPR: TrySetupProgressDialog");
string currentMessage = "Initializing..."; string currentMessage = "Initializing...";
string currentSubmessage = ""; string currentSubmessage = "";
@@ -142,7 +166,9 @@ public class OperationRunner
var pd = _progressDialog; var pd = _progressDialog;
_app.UiThreadHandler.Post(() => _app.UiThreadHandler.Post(() =>
{ {
Kp2aLog.Log("OPR: Starting TrySetupProgressDialog: Dismissing existing progress dialog");
pd.Dismiss(); pd.Dismiss();
Kp2aLog.Log("OPR: Finished TrySetupProgressDialog: Dismissing existing progress dialog");
}); });
} }
@@ -150,7 +176,7 @@ public class OperationRunner
_progressDialog = _app.CreateProgressDialog(_app.ActiveContext); _progressDialog = _app.CreateProgressDialog(_app.ActiveContext);
if (_progressDialog == null) if (_progressDialog == null)
{ {
Kp2aLog.Log("OperationRunner.TrySetupProgressDialog: _progressDialog is null"); Kp2aLog.Log("OPR: OperationRunner.TrySetupProgressDialog: _progressDialog is null");
return false; return false;
} }
@@ -164,9 +190,9 @@ public class OperationRunner
return true; return true;
} }
public void SetNewActiveContext(IKp2aApp app) public void SetNewActiveContext(IKp2aApp app)
{ {
Kp2aLog.Log("OPR: SetNewActiveContext: " + app.ActiveContext?.GetType().Name);
_app = app; _app = app;
Context? context = app.ActiveContext; Context? context = app.ActiveContext;
bool isAppContext = context == null || (context.ApplicationContext == context); bool isAppContext = context == null || (context.ApplicationContext == context);
@@ -179,9 +205,15 @@ public class OperationRunner
return; return;
} }
if (_currentlyRunningTask?.RunBlocking == true) if (_currentlyRunningTask?.RunBlocking == true && (context is Activity { IsFinishing: false, IsDestroyed:false}))
{ {
app.UiThreadHandler.Post(() => TrySetupProgressDialog()); Kp2aLog.Log("OPR: SetNewActiveContext: running blocking task, setting up progress dialog");
app.UiThreadHandler.Post(() =>
{
Kp2aLog.Log("OPR: Starting posted TrySetupProgressDialog");
TrySetupProgressDialog();
Kp2aLog.Log("OPR: Finished posted TrySetupProgressDialog");
});
} }
else else
{ {

View File

@@ -121,6 +121,7 @@ namespace keepass2android
{ {
_handler.Post(() => _handler.Post(() =>
{ {
Kp2aLog.Log("OPR: Starting posted SetMessage");
if (!String.IsNullOrEmpty(submessage)) if (!String.IsNullOrEmpty(submessage))
{ {
_progressDialog.SetMessage(_message + " (" + submessage + ")"); _progressDialog.SetMessage(_message + " (" + submessage + ")");
@@ -129,6 +130,7 @@ namespace keepass2android
{ {
_progressDialog.SetMessage(_message); _progressDialog.SetMessage(_message);
} }
Kp2aLog.Log("OPR: Finished posted SetMessage");
} }
); );
} }
@@ -138,9 +140,10 @@ namespace keepass2android
{ {
_handler.Post(() => _handler.Post(() =>
{ {
Kp2aLog.Log("OPR: Starting posted Show");
_progressDialog?.Show(); _progressDialog?.Show();
Kp2aLog.Log("OPR: Finished posted Show");
}); });
} }
@@ -148,9 +151,10 @@ namespace keepass2android
{ {
_handler.Post(() => _handler.Post(() =>
{ {
Kp2aLog.Log("OPR: Starting posted Dismiss");
_progressDialog?.Dismiss(); _progressDialog?.Dismiss();
Kp2aLog.Log("OPR: Finished posted Dismiss");
}); });
} }
@@ -160,7 +164,12 @@ namespace keepass2android
_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(() =>
{
Kp2aLog.Log("OPR: Starting posted SetMessage");
_progressDialog.SetMessage(message);
Kp2aLog.Log("OPR: Finishing posted SetMessage");
});
} }
} }

View File

@@ -66,12 +66,12 @@ namespace keepass2android
var baseVersionHash = cachingFileStorage.GetBaseVersionHash(ioc); var baseVersionHash = cachingFileStorage.GetBaseVersionHash(ioc);
Kp2aLog.Log("Checking for file change. baseVersionHash = " + baseVersionHash); Kp2aLog.Log("Checking for file change. baseVersionHash = " + baseVersionHash);
if (baseVersionHash != hash || if (baseVersionHash != hash ||
false //TODO remove true //TODO remove
) )
{ {
//remote file is modified //remote file is modified
if (cachingFileStorage.HasLocalChanges(ioc) if (cachingFileStorage.HasLocalChanges(ioc)
|| false //TODO remove || true //TODO remove
) )
{ {
//conflict! need to merge //conflict! need to merge

View File

@@ -45,10 +45,17 @@ namespace keepass2android
Message = ""; Message = "";
if (Handler != null) if (Handler != null)
{ {
Handler.Post(() => {_actionToPerform(Success, Message, ActiveContext);}); Handler.Post(() =>
{
Kp2aLog.Log("OPR: Starting posted actionToPerform ");
_actionToPerform(Success, Message, ActiveContext);
Kp2aLog.Log("OPR: Finished posted actionToPerform ");
});
} }
else else
_actionToPerform(Success, Message, ActiveContext); {
_actionToPerform(Success, Message, ActiveContext);
}
base.Run(); base.Run();
} }
} }
@@ -80,7 +87,7 @@ namespace keepass2android
{ {
_app.RegisterPendingActionForContextInstance(_contextInstanceId, this); _app.RegisterPendingActionForContextInstance(_contextInstanceId, this);
} }
else base.Run(); else _app.UiThreadHandler.Post(() => base.Run());
} }
} }

View File

@@ -97,9 +97,16 @@ namespace keepass2android
if (NextOnOperationFinishedHandler == null) return; if (NextOnOperationFinishedHandler == null) return;
// Pass on result on call finish // Pass on result on call finish
NextOnOperationFinishedHandler.SetResult(Success, Message, ImportantMessage, Exception); NextOnOperationFinishedHandler.SetResult(Success, Message, ImportantMessage, Exception);
if ( Handler != null ) { var handler = Handler ?? NextOnOperationFinishedHandler.Handler ?? null;
Handler.Post(NextOnOperationFinishedHandler.Run);
if (handler != null ) {
handler.Post(() =>
{
Kp2aLog.Log("OPR: Starting posted NextOnOperationFinishedHandler");
NextOnOperationFinishedHandler.Run();
Kp2aLog.Log("OPR: Finished posted NextOnOperationFinishedHandler");
});
} else { } else {
NextOnOperationFinishedHandler.Run(); NextOnOperationFinishedHandler.Run();
} }

View File

@@ -104,8 +104,6 @@ namespace keepass2android
protected override void OnStart() protected override void OnStart()
{ {
App.Kp2a.ActiveContext = this; App.Kp2a.ActiveContext = this;
OperationRunner.Instance.SetNewActiveContext( App.Kp2a);
base.OnStart(); base.OnStart();
Kp2aLog.Log(ClassName + ".OnStart" + " " + ID); Kp2aLog.Log(ClassName + ".OnStart" + " " + ID);
} }

View File

@@ -56,7 +56,7 @@ namespace keepass2android
OperationWithFinishHandler task; OperationWithFinishHandler task;
OnOperationFinishedHandler onOperationFinishedHandler = new ActionInContextInstanceOnOperationFinished(_activity.ContextInstanceId, App.Kp2a, (success, message, context) => OnOperationFinishedHandler onOperationFinishedHandler = new ActionInContextInstanceOnOperationFinished(_activity.ContextInstanceId, App.Kp2a, (success, message, context) =>
{ {
new Handler(Looper.MainLooper).Post(() => App.Kp2a.UiThreadHandler.Post(() =>
{ {
if (!String.IsNullOrEmpty(message)) if (!String.IsNullOrEmpty(message))
App.Kp2a.ShowMessage(context, message, success ? MessageSeverity.Info : MessageSeverity.Error); App.Kp2a.ShowMessage(context, message, success ? MessageSeverity.Info : MessageSeverity.Error);

View File

@@ -651,97 +651,108 @@ namespace keepass2android
{ {
if (app.ActiveContext is Activity activity) if (app.ActiveContext is Activity activity)
{ {
Handler handler = new Handler(Looper.MainLooper); Kp2aLog.Log("OPR: Will show YesNoCancel dialog because active context is an Activity.");
handler.Post(() =>
if (_dialog is { IsShowing: true })
{ {
if (_dialog is { IsShowing: true })
{
_dialog.Dismiss();
_dialog = null;
}
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity);
builder.SetTitle(app.GetResourceString(TitleKey));
builder.SetMessage(app.GetResourceString(MessageKey) +
(MessageSuffix != "" ? " " + MessageSuffix : ""));
// _____handlerWithShow are wrappers around given handlers to update _isSHowingYesNoCancelDialog
// and to show progress dialog after yesNoCancel dialog is closed
EventHandler<DialogClickEventArgs> yesHandlerWithShow = (sender, args) =>
{
onUserInputDialogClose();
YesHandler.Invoke(sender, args);
};
string yesText = app.GetResourceString(YesString);
builder.SetPositiveButton(yesText, yesHandlerWithShow);
string noText = "";
if (NoHandler != null)
{
EventHandler<DialogClickEventArgs> noHandlerWithShow = (sender, args) =>
{
onUserInputDialogClose();
NoHandler.Invoke(sender, args);
};
noText = app.GetResourceString(NoString);
builder.SetNegativeButton(noText, noHandlerWithShow);
}
string cancelText = "";
if (CancelHandler != null)
{
EventHandler<DialogClickEventArgs> cancelHandlerWithShow = (sender, args) =>
{
onUserInputDialogClose();
CancelHandler.Invoke(sender, args);
};
cancelText = App.Context.GetString(Android.Resource.String.Cancel);
builder.SetNeutralButton(cancelText,
cancelHandlerWithShow);
}
_dialog = builder.Create();
if (DismissHandler != null)
{
_dialog.SetOnDismissListener(new Util.DismissListener(() =>
{
onUserInputDialogClose();
DismissHandler(_dialog, EventArgs.Empty);
}));
}
onUserInputDialogShow();
try try
{ {
_dialog.Show(); _dialog.Dismiss();
}
catch (Exception e)
{
Kp2aLog.LogUnexpectedError(e);
}
_dialog = null;
}
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity);
builder.SetTitle(app.GetResourceString(TitleKey));
builder.SetMessage(app.GetResourceString(MessageKey) +
(MessageSuffix != "" ? " " + MessageSuffix : ""));
// _____handlerWithShow are wrappers around given handlers to update _isSHowingYesNoCancelDialog
// and to show progress dialog after yesNoCancel dialog is closed
EventHandler<DialogClickEventArgs> yesHandlerWithShow = (sender, args) =>
{
onUserInputDialogClose();
YesHandler.Invoke(sender, args);
};
string yesText = app.GetResourceString(YesString);
builder.SetPositiveButton(yesText, yesHandlerWithShow);
string noText = "";
if (NoHandler != null)
{
EventHandler<DialogClickEventArgs> noHandlerWithShow = (sender, args) =>
{
onUserInputDialogClose();
NoHandler.Invoke(sender, args);
};
noText = app.GetResourceString(NoString);
builder.SetNegativeButton(noText, noHandlerWithShow);
}
string cancelText = "";
if (CancelHandler != null)
{
EventHandler<DialogClickEventArgs> cancelHandlerWithShow = (sender, args) =>
{
onUserInputDialogClose();
CancelHandler.Invoke(sender, args);
};
cancelText = App.Context.GetString(Android.Resource.String.Cancel);
builder.SetNeutralButton(cancelText,
cancelHandlerWithShow);
}
_dialog = builder.Create();
if (DismissHandler != null)
{
_dialog.SetOnDismissListener(new Util.DismissListener(() =>
{
onUserInputDialogClose();
DismissHandler(_dialog, EventArgs.Empty);
}));
}
else
{
_dialog.SetCancelable(false);
}
onUserInputDialogShow();
try
{
_dialog.Show();
}
catch (Exception e)
{
Kp2aLog.LogUnexpectedError(e);
}
if (yesText.Length + noText.Length + cancelText.Length >= 20)
{
try
{
Button button = _dialog.GetButton((int)DialogButtonType.Positive);
LinearLayout linearLayout = (LinearLayout)button.Parent;
linearLayout.Orientation = Orientation.Vertical;
} }
catch (Exception e) catch (Exception e)
{ {
Kp2aLog.LogUnexpectedError(e); Kp2aLog.LogUnexpectedError(e);
} }
}
if (yesText.Length + noText.Length + cancelText.Length >= 20)
{
try
{
Button button = _dialog.GetButton((int)DialogButtonType.Positive);
LinearLayout linearLayout = (LinearLayout)button.Parent;
linearLayout.Orientation = Orientation.Vertical;
}
catch (Exception e)
{
Kp2aLog.LogUnexpectedError(e);
}
}
});
return true; return true;
} }
else else
{ {
Kp2aLog.Log("OPR: Cannot show YesNoCancel dialog because active context is not an Activity.");
OperationRunner.Instance.StatusLogger?.UpdateSubMessage(App.Context.GetString(Resource.String.user_interaction_required)); OperationRunner.Instance.StatusLogger?.UpdateSubMessage(App.Context.GetString(Resource.String.user_interaction_required));
return false; return false;
} }
@@ -759,6 +770,7 @@ namespace keepass2android
{ {
throw new Java.Lang.InterruptedException(); throw new Java.Lang.InterruptedException();
} }
Kp2aLog.Log("OPR: AskYesNoCancel called with titleKey " + titleKey + ", messageKey " + messageKey);
_currentlyPendingYesNoCancelQuestion = new YesNoCancelQuestion() _currentlyPendingYesNoCancelQuestion = new YesNoCancelQuestion()
{ {
@@ -772,8 +784,13 @@ namespace keepass2android
DismissHandler = dismissHandler, DismissHandler = dismissHandler,
MessageSuffix = messageSuffix MessageSuffix = messageSuffix
}; };
_currentlyPendingYesNoCancelQuestion.TryShow(this, OnUserInputDialogClose, OnUserInputDialogShow); UiThreadHandler.Post( () =>
{
Kp2aLog.Log("OPR: Starting posted AskYesNoCancel/TryShow ");
_currentlyPendingYesNoCancelQuestion.TryShow(this, OnUserInputDialogClose, OnUserInputDialogShow);
Kp2aLog.Log("OPR: Finished posted AskYesNoCancel/TryShow ");
});
} }
@@ -785,18 +802,22 @@ namespace keepass2android
/// because they are just progress indicators. /// because they are just progress indicators.
/// </summary> /// </summary>
private void ShowAllActiveProgressDialogs() private void ShowAllActiveProgressDialogs()
{ {
Kp2aLog.Log("OPR: ShowAllActiveProgressDialogs");
foreach (RealProgressDialog progressDialog in _activeProgressDialogs) foreach (RealProgressDialog progressDialog in _activeProgressDialogs)
{ {
progressDialog.Show(); Kp2aLog.Log("OPR: ShowAllActiveProgressDialogs: Showing " + progressDialog.GetType().Name);
progressDialog.Show();
} }
} }
private void HideAllActiveProgressDialogs() private void HideAllActiveProgressDialogs()
{ {
foreach (RealProgressDialog progressDialog in _activeProgressDialogs) Kp2aLog.Log("OPR: HideAllActiveProgressDialogs");
foreach (RealProgressDialog progressDialog in _activeProgressDialogs)
{ {
progressDialog.Hide(); Kp2aLog.Log("OPR: HideAllActiveProgressDialogs: Hiding " + progressDialog.GetType().Name);
progressDialog.Hide();
} }
} }
@@ -816,6 +837,7 @@ namespace keepass2android
private void OnUserInputDialogClose() private void OnUserInputDialogClose()
{ {
_isShowingUserInputDialog = false; _isShowingUserInputDialog = false;
Kp2aLog.Log("OPR: OnUserInputDialogClose called, _currentlyPendingYesNoCancelQuestion is " + (_currentlyPendingYesNoCancelQuestion != null ? "not null" : "null") + " Will set to null now.");
_currentlyPendingYesNoCancelQuestion = null; _currentlyPendingYesNoCancelQuestion = null;
ShowAllActiveProgressDialogs(); ShowAllActiveProgressDialogs();
@@ -869,8 +891,17 @@ namespace keepass2android
_app._activeProgressDialogs.Add(this); _app._activeProgressDialogs.Add(this);
// Only show if asking dialog not also showing // Only show if asking dialog not also showing
if (!_app._isShowingUserInputDialog) if (!_app._isShowingUserInputDialog)
{ {
_pd.Show(); Kp2aLog.Log("OPR: Showing progress dialog " + _pd.GetType().Name);
try
{
_pd.Show();
}
catch (Exception e)
{
Kp2aLog.LogUnexpectedError(e);
}
} }
} }
@@ -882,6 +913,7 @@ namespace keepass2android
public IProgressDialog CreateProgressDialog(Context ctx) public IProgressDialog CreateProgressDialog(Context ctx)
{ {
Kp2aLog.Log("OPR: CreateProgressDialog called with ctx " + ctx?.GetType().Name);
/*Kp2aLog.Log("ctx is null ? " + (ctx == null)); /*Kp2aLog.Log("ctx is null ? " + (ctx == null));
Kp2aLog.Log("ctx is activity ? " + (ctx is Activity)); Kp2aLog.Log("ctx is activity ? " + (ctx is Activity));
Kp2aLog.Log("ctx is destroyed ? " + (ctx is Activity { IsDestroyed: true })); Kp2aLog.Log("ctx is destroyed ? " + (ctx is Activity { IsDestroyed: true }));
@@ -897,12 +929,13 @@ namespace keepass2android
} }
catch (Exception e) catch (Exception e)
{ {
//may happen if the activity is (being) destroyed //may happen if the activity is (being) destroyed
Kp2aLog.Log("OPR: CreateProgressDialog failed with " + e.ToString());
return null; return null;
} }
} }
public IFileStorage GetFileStorage(IOConnectionInfo iocInfo) public IFileStorage GetFileStorage(IOConnectionInfo iocInfo)
{ {
@@ -1306,7 +1339,9 @@ namespace keepass2android
public void StartBackgroundSyncService() public void StartBackgroundSyncService()
{ {
Intent intent = new Intent(App.Context, typeof(BackgroundSyncService)); Kp2aLog.Log("OPR: StartBackgroundSyncService");
Intent intent = new Intent(App.Context, typeof(BackgroundSyncService));
intent.SetAction(BackgroundSyncService.ActionStart); intent.SetAction(BackgroundSyncService.ActionStart);
App.Context.StartService(intent); App.Context.StartService(intent);
} }
@@ -1504,8 +1539,10 @@ namespace keepass2android
get => _activeContext ?? Application.Context; get => _activeContext ?? Application.Context;
set set
{ {
_activeContext = value; _activeContext = value;
_currentlyPendingYesNoCancelQuestion?.TryShow(this, OnUserInputDialogClose, OnUserInputDialogClose); OperationRunner.Instance.SetNewActiveContext(App.Kp2a);
Kp2aLog.Log("OPR: ActiveContext set to " + _activeContext?.GetType().Name + ". Pending question? " +(_currentlyPendingYesNoCancelQuestion != null));
_currentlyPendingYesNoCancelQuestion?.TryShow(this, OnUserInputDialogClose, OnUserInputDialogShow);
} }
} }
@@ -1650,9 +1687,6 @@ namespace keepass2android
{ {
Kp2aLog.Log("Going to background"); Kp2aLog.Log("Going to background");
Kp2a.ActiveContext = null; Kp2a.ActiveContext = null;
// notify background operation runner that there is currently no active context, i.e. no UI where status can be displayed.
// The OperationRunner will launch the background sync service if a task (even a blocking one) is running currently.
OperationRunner.Instance.SetNewActiveContext(Kp2a);
} }

View File

@@ -52,7 +52,6 @@ namespace keepass2android.services
CreateNotificationChannel(); CreateNotificationChannel();
StartForeground(NotificationId, BuildNotification()); StartForeground(NotificationId, BuildNotification());
App.Kp2a.ActiveContext = this; App.Kp2a.ActiveContext = this;
OperationRunner.Instance.SetNewActiveContext(App.Kp2a);
return StartCommandResult.Sticky; return StartCommandResult.Sticky;
} }
catch (Exception ex) catch (Exception ex)

View File

@@ -42,29 +42,34 @@ public class BackgroundOperationContainer : LinearLayout, IProgressUi
public void Show() public void Show()
{ {
new Handler(Looper.MainLooper).Post(() => App.Kp2a.UiThreadHandler.Post(() =>
{ {
Kp2aLog.Log("OPR: Starting posted Show ");
Visibility = ViewStates.Visible; Visibility = ViewStates.Visible;
FindViewById<TextView>(Resource.Id.background_ops_message)!.Visibility = ViewStates.Gone; FindViewById<TextView>(Resource.Id.background_ops_message)!.Visibility = ViewStates.Gone;
FindViewById<TextView>(Resource.Id.background_ops_submessage)!.Visibility = ViewStates.Gone; FindViewById<TextView>(Resource.Id.background_ops_submessage)!.Visibility = ViewStates.Gone;
Kp2aLog.Log("OPR: Finished posted Show ");
}); });
} }
public void Hide() public void Hide()
{ {
new Handler(Looper.MainLooper).Post(() => App.Kp2a.UiThreadHandler.Post(() =>
{ {
Kp2aLog.Log("OPR: Starting posted Hide ");
String activityType = Context.GetType().FullName; String activityType = Context.GetType().FullName;
Kp2aLog.Log("Hiding background ops container in" + activityType); Kp2aLog.Log("Hiding background ops container in" + activityType);
Visibility = ViewStates.Gone; Visibility = ViewStates.Gone;
Kp2aLog.Log("OPR: Finished posted Hide ");
}); });
} }
public void UpdateMessage(string message) public void UpdateMessage(string message)
{ {
new Handler(Looper.MainLooper).Post(() => App.Kp2a.UiThreadHandler.Post(() =>
{ {
Kp2aLog.Log("OPR: Starting posted UpdateMessage ");
TextView messageTextView = FindViewById<TextView>(Resource.Id.background_ops_message)!; TextView messageTextView = FindViewById<TextView>(Resource.Id.background_ops_message)!;
if (string.IsNullOrEmpty(message)) if (string.IsNullOrEmpty(message))
{ {
@@ -75,13 +80,15 @@ public class BackgroundOperationContainer : LinearLayout, IProgressUi
messageTextView.Visibility = ViewStates.Visible; messageTextView.Visibility = ViewStates.Visible;
messageTextView.Text = message; messageTextView.Text = message;
} }
Kp2aLog.Log("OPR: Finished posted UpdateMessage ");
}); });
} }
public void UpdateSubMessage(string submessage) public void UpdateSubMessage(string submessage)
{ {
new Handler(Looper.MainLooper).Post(() => App.Kp2a.UiThreadHandler.Post(() =>
{ {
Kp2aLog.Log("OPR: Starting posted UpdateSubMessage ");
TextView subMessageTextView = FindViewById<TextView>(Resource.Id.background_ops_submessage)!; TextView subMessageTextView = FindViewById<TextView>(Resource.Id.background_ops_submessage)!;
if (string.IsNullOrEmpty(submessage)) if (string.IsNullOrEmpty(submessage))
{ {
@@ -92,6 +99,7 @@ public class BackgroundOperationContainer : LinearLayout, IProgressUi
subMessageTextView.Visibility = ViewStates.Visible; subMessageTextView.Visibility = ViewStates.Visible;
subMessageTextView.Text = submessage; subMessageTextView.Text = submessage;
} }
Kp2aLog.Log("OPR: Finished posted UpdateSubMessage ");
}); });
} }
} }

View File

@@ -125,94 +125,103 @@ namespace keepass2android.view
if (_groupBaseActivity.IsFinishing) if (_groupBaseActivity.IsFinishing)
return; return;
_entry = pw; _entry = pw;
_pos = pos; _pos = pos;
ev.FindViewById(Resource.Id.icon).Visibility = ViewStates.Visible; try
ev.FindViewById(Resource.Id.check_mark).Visibility = ViewStates.Invisible; {
_db = App.Kp2a.FindDatabaseForElement(_entry);
ImageView iv = (ImageView)ev.FindViewById(Resource.Id.icon);
bool isExpired = pw.Expires && pw.ExpiryTime < DateTime.Now;
if (isExpired)
{
_db.DrawableFactory.AssignDrawableTo(iv, Context, _db.KpDatabase, PwIcon.Expired, PwUuid.Zero, false);
} else
{
_db.DrawableFactory.AssignDrawableTo(iv, Context, _db.KpDatabase, pw.IconId, pw.CustomIconUuid, false);
}
String title = pw.Strings.ReadSafe(PwDefs.TitleField);
title = SprEngine.Compile(title, new SprContext(_entry, _db.KpDatabase, SprCompileFlags.All));
var str = new SpannableString(title);
if (isExpired)
{
str.SetSpan(new StrikethroughSpan(), 0, title.Length, SpanTypes.ExclusiveExclusive);
}
_textView.TextFormatted = str;
if (_defaultTextColor == null)
_defaultTextColor = _textView.TextColors.DefaultColor;
if (_groupActivity.IsBeingMoved(_entry.Uuid))
{
int elementBeingMoved = Context.Resources.GetColor(Resource.Color.md_theme_inversePrimary);
_textView.SetTextColor(new Color(elementBeingMoved));
}
else
_textView.SetTextColor(new Color((int)_defaultTextColor));
String detail = pw.Strings.ReadSafe(PwDefs.UserNameField);
detail = SprEngine.Compile(detail, new SprContext(_entry, _db.KpDatabase, SprCompileFlags.All));
if ((_showDetail == false) || (String.IsNullOrEmpty(detail)))
{
_textviewDetails.Visibility = ViewStates.Gone;
}
else
{
var strDetail = new SpannableString(detail);
if (isExpired)
{
strDetail.SetSpan(new StrikethroughSpan(), 0, detail.Length, SpanTypes.ExclusiveExclusive);
}
_textviewDetails.TextFormatted = strDetail;
_textviewDetails.Visibility = ViewStates.Visible;
}
if ( (!_showGroupFullPath) || (!_isSearchResult) ) {
_textgroupFullPath.Visibility = ViewStates.Gone;
}
else {
String groupDetail = pw.ParentGroup.GetFullPath();
if (App.Kp2a.OpenDatabases.Count() > 1)
{
groupDetail += "(" + App.Kp2a.GetFileStorage(_db.Ioc).GetDisplayName(_db.Ioc) + ")";
}
var strGroupDetail = new SpannableString (groupDetail);
if (isExpired) {
strGroupDetail.SetSpan (new StrikethroughSpan (), 0, groupDetail.Length, SpanTypes.ExclusiveExclusive);
}
_textgroupFullPath.TextFormatted = strGroupDetail;
_textgroupFullPath.Visibility = ViewStates.Visible;
}
//try to get totp data
UpdateTotp();
ev.FindViewById(Resource.Id.icon).Visibility = ViewStates.Visible;
ev.FindViewById(Resource.Id.check_mark).Visibility = ViewStates.Invisible;
_db = App.Kp2a.FindDatabaseForElement(_entry);
ImageView iv = (ImageView)ev.FindViewById(Resource.Id.icon);
bool isExpired = pw.Expires && pw.ExpiryTime < DateTime.Now;
if (isExpired)
{
_db.DrawableFactory.AssignDrawableTo(iv, Context, _db.KpDatabase, PwIcon.Expired, PwUuid.Zero, false);
}
else
{
_db.DrawableFactory.AssignDrawableTo(iv, Context, _db.KpDatabase, pw.IconId, pw.CustomIconUuid, false);
}
String title = pw.Strings.ReadSafe(PwDefs.TitleField);
title = SprEngine.Compile(title, new SprContext(_entry, _db.KpDatabase, SprCompileFlags.All));
var str = new SpannableString(title);
if (isExpired)
{
str.SetSpan(new StrikethroughSpan(), 0, title.Length, SpanTypes.ExclusiveExclusive);
}
_textView.TextFormatted = str;
if (_defaultTextColor == null)
_defaultTextColor = _textView.TextColors.DefaultColor;
if (_groupActivity.IsBeingMoved(_entry.Uuid))
{
int elementBeingMoved = Context.Resources.GetColor(Resource.Color.md_theme_inversePrimary);
_textView.SetTextColor(new Color(elementBeingMoved));
}
else
_textView.SetTextColor(new Color((int)_defaultTextColor));
String detail = pw.Strings.ReadSafe(PwDefs.UserNameField);
detail = SprEngine.Compile(detail, new SprContext(_entry, _db.KpDatabase, SprCompileFlags.All));
if ((_showDetail == false) || (String.IsNullOrEmpty(detail)))
{
_textviewDetails.Visibility = ViewStates.Gone;
}
else
{
var strDetail = new SpannableString(detail);
if (isExpired)
{
strDetail.SetSpan(new StrikethroughSpan(), 0, detail.Length, SpanTypes.ExclusiveExclusive);
}
_textviewDetails.TextFormatted = strDetail;
_textviewDetails.Visibility = ViewStates.Visible;
}
if ((!_showGroupFullPath) || (!_isSearchResult))
{
_textgroupFullPath.Visibility = ViewStates.Gone;
}
else
{
String groupDetail = pw.ParentGroup.GetFullPath();
if (App.Kp2a.OpenDatabases.Count() > 1)
{
groupDetail += "(" + App.Kp2a.GetFileStorage(_db.Ioc).GetDisplayName(_db.Ioc) + ")";
}
var strGroupDetail = new SpannableString(groupDetail);
if (isExpired)
{
strGroupDetail.SetSpan(new StrikethroughSpan(), 0, groupDetail.Length, SpanTypes.ExclusiveExclusive);
}
_textgroupFullPath.TextFormatted = strGroupDetail;
_textgroupFullPath.Visibility = ViewStates.Visible;
}
//try to get totp data
UpdateTotp();
}
catch (Exception e)
{
Kp2aLog.LogUnexpectedError(e);
}
} }