add support for yes/no/cancel question during background sync

This commit is contained in:
Philipp Crocoll
2025-05-13 16:42:50 +02:00
parent 8277283ebc
commit 41e6e67e87
7 changed files with 175 additions and 107 deletions

View File

@@ -45,6 +45,8 @@ namespace keepass2android
} }
} }
public ProgressUiAsStatusLoggerAdapter StatusLogger => _statusLogger;
private BackgroundOperationRunner() private BackgroundOperationRunner()
{ {
//private constructor //private constructor
@@ -77,7 +79,6 @@ namespace keepass2android
if (!_taskQueue.Any()) if (!_taskQueue.Any())
{ {
_thread = null; _thread = null;
_currentlyRunningTask = null;
_statusLogger.EndLogging(); _statusLogger.EndLogging();
break; break;
} }
@@ -86,7 +87,18 @@ namespace keepass2android
_currentlyRunningTask = _taskQueue.Dequeue(); _currentlyRunningTask = _taskQueue.Dequeue();
} }
} }
var originalFinishedHandler = _currentlyRunningTask.operationFinishedHandler;
_currentlyRunningTask.operationFinishedHandler = new ActionOnOperationFinished(app, (
(success, message, context) =>
{
_currentlyRunningTask = null;
}), originalFinishedHandler);
_currentlyRunningTask.Run(); _currentlyRunningTask.Run();
while (_currentlyRunningTask != null)
{
Thread.Sleep(100);
}
} }
}); });
@@ -248,13 +260,7 @@ namespace keepass2android
get { return _activeActivity; } get { return _activeActivity; }
private set private set
{ {
if (_activeActivity != null && _activeActivity != _previouslyActiveContext) _activeActivity = value;
{
_previouslyActiveContext = _activeActivity;
}
_activeActivity = value;
if (_activeActivity != null) if (_activeActivity != null)
{ {
@@ -264,18 +270,12 @@ namespace keepass2android
} }
} }
public Context PreviouslyActiveContext private readonly Handler _handler;
{
get { return _previouslyActiveContext; }
}
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, _previouslyActiveContext; private Context _activeActivity;
private ProgressDialogStatusLogger _progressDialogStatusLogger; private ProgressDialogStatusLogger _progressDialogStatusLogger;
public BlockingOperationRunner(IKp2aApp app, OperationWithFinishHandler task) public BlockingOperationRunner(IKp2aApp app, OperationWithFinishHandler task)

View File

@@ -82,6 +82,8 @@ namespace keepass2android
} }
_saveDb = null; _saveDb = null;
}), _app.CurrentDb, false, remoteData); }), _app.CurrentDb, false, remoteData);
_saveDb.SetStatusLogger(StatusLogger);
_saveDb.DoNotSetStatusLoggerMessage = true; //Keep "sync db" as main message
_saveDb.SyncInBackground = false; _saveDb.SyncInBackground = false;
_saveDb.Run(); _saveDb.Run();

View File

@@ -38,9 +38,6 @@ namespace keepass2android
_actionToPerform = actionToPerform; _actionToPerform = actionToPerform;
} }
//if set to true, the previously active active will be passed to ActionToPerformOnFinish instead null if no activity is on foreground
public bool AllowInactiveActivity { get; set; }
public override void Run() public override void Run()
{ {
if (Message == null) if (Message == null)

View File

@@ -41,6 +41,8 @@ namespace keepass2android
private readonly bool _dontSave; private readonly bool _dontSave;
private bool requiresSubsequentSync = false; //if true, we need to sync the file after saving. private bool requiresSubsequentSync = false; //if true, we need to sync the file after saving.
public bool DoNotSetStatusLoggerMessage = false;
/// <summary> /// <summary>
/// stream for reading the data from the original file. If this is set to a non-null value, we know we need to sync /// stream for reading the data from the original file. If this is set to a non-null value, we know we need to sync
/// </summary> /// </summary>
@@ -104,9 +106,12 @@ namespace keepass2android
if (ShowDatabaseIocInStatus) if (ShowDatabaseIocInStatus)
message += " (" + _app.GetFileStorage(_db.Ioc).GetDisplayName(_db.Ioc) + ")"; message += " (" + _app.GetFileStorage(_db.Ioc).GetDisplayName(_db.Ioc) + ")";
StatusLogger.UpdateMessage(message); if (!DoNotSetStatusLoggerMessage)
{
StatusLogger.UpdateMessage(message);
}
IOConnectionInfo ioc = _db.Ioc; IOConnectionInfo ioc = _db.Ioc;
IFileStorage fileStorage = _app.GetFileStorage(ioc); IFileStorage fileStorage = _app.GetFileStorage(ioc);
if (SyncInBackground && fileStorage is IOfflineSwitchable offlineSwitchable) if (SyncInBackground && fileStorage is IOfflineSwitchable offlineSwitchable)

View File

@@ -1305,7 +1305,7 @@ namespace keepass2android
{ {
if (Success) if (Success)
{ {
((GroupBaseActivity)ActiveContext)?.RefreshIfDirty(); (ActiveContext as GroupBaseActivity)?.RefreshIfDirty();
} }
else else
{ {
@@ -1325,7 +1325,7 @@ namespace keepass2android
{ {
if (Success) if (Success)
{ {
((GroupBaseActivity)ActiveContext)?.RefreshIfDirty(); (ActiveContext as GroupBaseActivity)?.RefreshIfDirty();
} }
else else
{ {

View File

@@ -1255,5 +1255,5 @@
<string name="switch_keyboard_for_totp_enabled">Note: You have enabled App - Password access - Autofill-Service - Autofill for TOTP entries. This can cause this window to show when you open an entry with a TOTP.</string> <string name="switch_keyboard_for_totp_enabled">Note: You have enabled App - Password access - Autofill-Service - Autofill for TOTP entries. This can cause this window to show when you open an entry with a TOTP.</string>
<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_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="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>
</resources> </resources>

View File

@@ -17,6 +17,7 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.Design;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Security; using System.Net.Security;
@@ -46,6 +47,7 @@ using keepass2android;
using keepass2android.Utils; using keepass2android.Utils;
using KeePassLib.Interfaces; using KeePassLib.Interfaces;
using KeePassLib.Utility; using KeePassLib.Utility;
using AlertDialog = AndroidX.AppCompat.App.AlertDialog;
using Message = keepass2android.Utils.Message; using Message = keepass2android.Utils.Message;
#if !NoNet #if !NoNet
#if !EXCLUDE_JAVAFILESTORAGE #if !EXCLUDE_JAVAFILESTORAGE
@@ -480,6 +482,8 @@ namespace keepass2android
// Whether the app is currently showing a dialog that requires user input, like a yesNoCancel dialog // Whether the app is currently showing a dialog that requires user input, like a yesNoCancel dialog
private bool _isShowingUserInputDialog = false; private bool _isShowingUserInputDialog = false;
private IMessagePresenter? _messagePresenter; private IMessagePresenter? _messagePresenter;
private YesNoCancelQuestion? _currentlyPendingYesNoCancelQuestion = null;
private Context _activeContext;
private void AskForReload(Activity activity, Action<bool> actionOnResult) private void AskForReload(Activity activity, Action<bool> actionOnResult)
{ {
@@ -591,92 +595,142 @@ namespace keepass2android
AskYesNoCancel(titleKey, messageKey, yesString, noString, yesHandler, noHandler, cancelHandler, null, messageSuffix); AskYesNoCancel(titleKey, messageKey, yesString, noString, yesHandler, noHandler, cancelHandler, null, messageSuffix);
} }
public void AskYesNoCancel(UiStringKey titleKey, UiStringKey messageKey, class YesNoCancelQuestion
{
private AlertDialog? _dialog;
public UiStringKey TitleKey { get; set; }
public UiStringKey MessageKey { get; set; }
public UiStringKey YesString { get; set; }
public UiStringKey NoString { get; set; }
public EventHandler<DialogClickEventArgs>? YesHandler { get; set; }
public EventHandler<DialogClickEventArgs>? NoHandler { get; set; }
public EventHandler<DialogClickEventArgs>? CancelHandler { get; set; }
public EventHandler DismissHandler { get; set; }
public string MessageSuffix { get; set; }
public bool TryShow(IKp2aApp app, Action onUserInputDialogClose, Action onUserInputDialogShow)
{
if (app.ActiveContext is Activity activity)
{
Handler handler = new Handler(Looper.MainLooper);
handler.Post(() =>
{
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
{
_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)
{
Kp2aLog.LogUnexpectedError(e);
}
}
});
return true;
}
else
{
BackgroundOperationRunner.Instance.StatusLogger?.UpdateSubMessage(App.Context.GetString(Resource.String.user_interaction_required));
return false;
}
}
}
public void AskYesNoCancel(UiStringKey titleKey, UiStringKey messageKey,
UiStringKey yesString, UiStringKey noString, UiStringKey yesString, UiStringKey noString,
EventHandler<DialogClickEventArgs> yesHandler, EventHandler<DialogClickEventArgs> yesHandler,
EventHandler<DialogClickEventArgs> noHandler, EventHandler<DialogClickEventArgs> noHandler,
EventHandler<DialogClickEventArgs> cancelHandler, EventHandler<DialogClickEventArgs> cancelHandler,
EventHandler dismissHandler,string messageSuffix = "") EventHandler dismissHandler,string messageSuffix = "")
{ {
Handler handler = new Handler(Looper.MainLooper); _currentlyPendingYesNoCancelQuestion = new YesNoCancelQuestion()
handler.Post(() => {
{ TitleKey = titleKey,
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(ActiveContext); MessageKey = messageKey,
builder.SetTitle(GetResourceString(titleKey)); YesString = yesString,
NoString = noString,
YesHandler = yesHandler,
NoHandler = noHandler,
CancelHandler = cancelHandler,
DismissHandler = dismissHandler,
MessageSuffix = messageSuffix
};
builder.SetMessage(GetResourceString(messageKey) + (messageSuffix != "" ? " " + messageSuffix : "")); _currentlyPendingYesNoCancelQuestion.TryShow(this, OnUserInputDialogClose, OnUserInputDialogShow);
// _____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 = GetResourceString(yesString);
builder.SetPositiveButton(yesText, yesHandlerWithShow);
string noText = "";
if (noHandler != null)
{
EventHandler<DialogClickEventArgs> noHandlerWithShow = (sender, args) =>
{
OnUserInputDialogClose();
noHandler.Invoke(sender, args);
};
noText = 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);
}
var dialog = builder.Create();
if (dismissHandler != null)
{
dialog.SetOnDismissListener(new Util.DismissListener(() => {
OnUserInputDialogClose();
dismissHandler(dialog, EventArgs.Empty);
}));
}
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)
{
Kp2aLog.LogUnexpectedError(e);
}
}
}
);
} }
/// <summary> /// <summary>
@@ -718,7 +772,9 @@ namespace keepass2android
private void OnUserInputDialogClose() private void OnUserInputDialogClose()
{ {
_isShowingUserInputDialog = false; _isShowingUserInputDialog = false;
ShowAllActiveProgressDialogs(); _currentlyPendingYesNoCancelQuestion = null;
ShowAllActiveProgressDialogs();
} }
public Handler UiThreadHandler public Handler UiThreadHandler
@@ -1369,7 +1425,15 @@ namespace keepass2android
} }
public Context ActiveContext { get; set; } public Context ActiveContext
{
get => _activeContext;
set
{
_activeContext = value;
_currentlyPendingYesNoCancelQuestion?.TryShow(this, OnUserInputDialogClose, OnUserInputDialogClose);
}
}
} }