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

View File

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

View File

@@ -38,9 +38,6 @@ namespace keepass2android
_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()
{
if (Message == null)

View File

@@ -41,6 +41,8 @@ namespace keepass2android
private readonly bool _dontSave;
private bool requiresSubsequentSync = false; //if true, we need to sync the file after saving.
public bool DoNotSetStatusLoggerMessage = false;
/// <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
/// </summary>
@@ -104,9 +106,12 @@ namespace keepass2android
if (ShowDatabaseIocInStatus)
message += " (" + _app.GetFileStorage(_db.Ioc).GetDisplayName(_db.Ioc) + ")";
StatusLogger.UpdateMessage(message);
IOConnectionInfo ioc = _db.Ioc;
if (!DoNotSetStatusLoggerMessage)
{
StatusLogger.UpdateMessage(message);
}
IOConnectionInfo ioc = _db.Ioc;
IFileStorage fileStorage = _app.GetFileStorage(ioc);
if (SyncInBackground && fileStorage is IOfflineSwitchable offlineSwitchable)

View File

@@ -1305,7 +1305,7 @@ namespace keepass2android
{
if (Success)
{
((GroupBaseActivity)ActiveContext)?.RefreshIfDirty();
(ActiveContext as GroupBaseActivity)?.RefreshIfDirty();
}
else
{
@@ -1325,7 +1325,7 @@ namespace keepass2android
{
if (Success)
{
((GroupBaseActivity)ActiveContext)?.RefreshIfDirty();
(ActiveContext as GroupBaseActivity)?.RefreshIfDirty();
}
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_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>
</resources>

View File

@@ -17,6 +17,7 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file
using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.IO;
using System.Linq;
using System.Net.Security;
@@ -46,6 +47,7 @@ using keepass2android;
using keepass2android.Utils;
using KeePassLib.Interfaces;
using KeePassLib.Utility;
using AlertDialog = AndroidX.AppCompat.App.AlertDialog;
using Message = keepass2android.Utils.Message;
#if !NoNet
#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
private bool _isShowingUserInputDialog = false;
private IMessagePresenter? _messagePresenter;
private YesNoCancelQuestion? _currentlyPendingYesNoCancelQuestion = null;
private Context _activeContext;
private void AskForReload(Activity activity, Action<bool> actionOnResult)
{
@@ -591,92 +595,142 @@ namespace keepass2android
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,
EventHandler<DialogClickEventArgs> yesHandler,
EventHandler<DialogClickEventArgs> noHandler,
EventHandler<DialogClickEventArgs> cancelHandler,
EventHandler dismissHandler,string messageSuffix = "")
{
Handler handler = new Handler(Looper.MainLooper);
handler.Post(() =>
{
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(ActiveContext);
builder.SetTitle(GetResourceString(titleKey));
builder.SetMessage(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 = 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);
}
}
}
);
_currentlyPendingYesNoCancelQuestion = new YesNoCancelQuestion()
{
TitleKey = titleKey,
MessageKey = messageKey,
YesString = yesString,
NoString = noString,
YesHandler = yesHandler,
NoHandler = noHandler,
CancelHandler = cancelHandler,
DismissHandler = dismissHandler,
MessageSuffix = messageSuffix
};
_currentlyPendingYesNoCancelQuestion.TryShow(this, OnUserInputDialogClose, OnUserInputDialogShow);
}
/// <summary>
@@ -718,7 +772,9 @@ namespace keepass2android
private void OnUserInputDialogClose()
{
_isShowingUserInputDialog = false;
ShowAllActiveProgressDialogs();
_currentlyPendingYesNoCancelQuestion = null;
ShowAllActiveProgressDialogs();
}
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);
}
}
}