diff --git a/src/Kp2aBusinessLogic/BlockingOperationRunner.cs b/src/Kp2aBusinessLogic/BlockingOperationRunner.cs
index 50896824..21024fe1 100644
--- a/src/Kp2aBusinessLogic/BlockingOperationRunner.cs
+++ b/src/Kp2aBusinessLogic/BlockingOperationRunner.cs
@@ -19,13 +19,199 @@ using Android.App;
using Android.Content;
using Android.OS;
using Java.Lang;
+using Java.Security;
+using KeePassLib.Interfaces;
+using System.Threading.Tasks;
+using Enum = System.Enum;
namespace keepass2android
{
- ///
- /// Class to run a task while a progress dialog is shown
- ///
- public class BlockingOperationRunner
+ public class BackgroundOperationRunner
+ {
+ //singleton instance
+ private static BackgroundOperationRunner _instance = null;
+
+ public static BackgroundOperationRunner Instance
+ {
+ get
+ {
+ if (_instance == null)
+ {
+ _instance = new BackgroundOperationRunner();
+ }
+
+ return _instance;
+ }
+ }
+
+ private BackgroundOperationRunner()
+ {
+ //private constructor
+ }
+
+ private readonly Queue _taskQueue = new Queue();
+ private readonly object _taskQueueLock = new object();
+ private Java.Lang.Thread? _thread = null;
+ private ProgressUiAsStatusLoggerAdapter _statusLogger = null;
+
+ public void Run(Activity activity, IKp2aApp app, OperationWithFinishHandler operation)
+ {
+ lock (Instance._taskQueueLock)
+ {
+ _taskQueue.Enqueue(operation);
+ SetNewActiveActivity(activity, app);
+
+ // Start thread to run the task (unless it's already running)
+ if (_thread == null)
+ {
+ _statusLogger.StartLogging("", false);
+ _thread = new Java.Lang.Thread(() =>
+ {
+ while (true)
+ {
+ OperationWithFinishHandler task;
+ lock (_taskQueueLock)
+ {
+ if (!_taskQueue.Any())
+ {
+ _thread = null;
+ _statusLogger.EndLogging();
+ break;
+ }
+ else
+ {
+ task = _taskQueue.Dequeue();
+ }
+ }
+ task.Run();
+ }
+
+ });
+ _thread.Start();
+ }
+
+ }
+
+ }
+
+ public void SetNewActiveActivity(Activity? activity, IKp2aApp app)
+ {
+ lock (_taskQueueLock)
+ {
+ var progressUi = (activity as IProgressUiProvider)?.ProgressUi;
+ if (_statusLogger == null)
+ {
+ _statusLogger = new ProgressUiAsStatusLoggerAdapter(progressUi, app);
+ }
+ else
+ {
+ _statusLogger.SetNewProgressUi(progressUi);
+ }
+
+ foreach (var task in _taskQueue)
+ {
+ task.ActiveActivity = activity;
+ task.SetStatusLogger(_statusLogger);
+ }
+
+ }
+
+
+ }
+ }
+
+ public class ProgressUiAsStatusLoggerAdapter : IKp2aStatusLogger
+ {
+ private IProgressUi? _progressUi;
+ private readonly IKp2aApp _app;
+
+ private string _lastMessage = "";
+ private string _lastSubMessage = "";
+ private bool _isVisible = false;
+
+ public ProgressUiAsStatusLoggerAdapter(IProgressUi progressUi, IKp2aApp app)
+ {
+ _progressUi = progressUi;
+ _app = app;
+ }
+
+ public void SetNewProgressUi(IProgressUi progressUi)
+ {
+ _progressUi = progressUi;
+ if (_isVisible)
+ {
+ progressUi?.Show();
+ progressUi?.UpdateMessage(_lastMessage);
+ progressUi?.UpdateSubMessage(_lastSubMessage);
+ }
+ else
+ {
+ progressUi?.Hide();
+ }
+ }
+
+ public void StartLogging(string strOperation, bool bWriteOperationToLog)
+ {
+ _progressUi?.Show();
+ _isVisible = true;
+ }
+
+ public void EndLogging()
+ {
+ _progressUi?.Hide();
+ _isVisible = false;
+ }
+
+ 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));
+ return true;
+ }
+ }
+ UpdateMessage(strNewText);
+
+ return true;
+ }
+
+ public void UpdateMessage(string message)
+ {
+ _progressUi?.UpdateMessage(message);
+ _lastMessage = message;
+ }
+
+ public void UpdateSubMessage(string submessage)
+ {
+ _progressUi?.UpdateSubMessage(submessage);
+ _lastSubMessage = submessage;
+ }
+
+ public bool ContinueWork()
+ {
+ return true;
+ }
+
+ public void UpdateMessage(UiStringKey stringKey)
+ {
+ if (_app != null)
+ UpdateMessage(_app.GetResourceString(stringKey));
+ }
+ }
+
+
+ ///
+ /// Class to run a task while a progress dialog is shown
+ ///
+ public class BlockingOperationRunner
{
//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;
diff --git a/src/Kp2aBusinessLogic/ProgressDialogStatusLogger.cs b/src/Kp2aBusinessLogic/ProgressDialogStatusLogger.cs
index d67b9f51..280a1795 100644
--- a/src/Kp2aBusinessLogic/ProgressDialogStatusLogger.cs
+++ b/src/Kp2aBusinessLogic/ProgressDialogStatusLogger.cs
@@ -27,6 +27,20 @@ namespace keepass2android
void UpdateMessage(UiStringKey stringKey);
}
+ public interface IProgressUi
+ {
+ void Show();
+ void Hide();
+ void UpdateMessage(String message);
+ void UpdateSubMessage(String submessage);
+ }
+
+ public interface IProgressUiProvider
+ {
+ IProgressUi? ProgressUi { get; }
+ }
+
+
public class Kp2aNullStatusLogger : IKp2aStatusLogger
{
public void StartLogging(string strOperation, bool bWriteOperationToLog)
diff --git a/src/keepass2android-app/EntryActivity.cs b/src/keepass2android-app/EntryActivity.cs
index b3015804..3dfe892b 100644
--- a/src/keepass2android-app/EntryActivity.cs
+++ b/src/keepass2android-app/EntryActivity.cs
@@ -56,6 +56,7 @@ using Android.Util;
using AndroidX.Core.Content;
using Google.Android.Material.Dialog;
using keepass2android;
+using keepass2android.views;
namespace keepass2android
{
@@ -97,7 +98,7 @@ namespace keepass2android
[Activity (Label = "@string/app_name", ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden,
Theme = "@style/Kp2aTheme_ActionBar")]
- public class EntryActivity : LockCloseActivity
+ public class EntryActivity : LockCloseActivity, IProgressUiProvider
{
public const String KeyEntry = "entry";
public const String KeyRefreshPos = "refresh_pos";
@@ -1603,5 +1604,7 @@ namespace keepass2android
imageViewerIntent.PutExtra("EntryKey", key);
StartActivity(imageViewerIntent);
}
- }
+
+ public IProgressUi? ProgressUi => FindViewById(Resource.Id.background_ops_container);
+ }
}
\ No newline at end of file
diff --git a/src/keepass2android-app/GroupBaseActivity.cs b/src/keepass2android-app/GroupBaseActivity.cs
index 43a8396e..09614b20 100644
--- a/src/keepass2android-app/GroupBaseActivity.cs
+++ b/src/keepass2android-app/GroupBaseActivity.cs
@@ -43,11 +43,12 @@ using keepass2android;
using KeeTrayTOTP.Libraries;
using AndroidX.AppCompat.Widget;
using Google.Android.Material.Dialog;
+using keepass2android.views;
using SearchView = AndroidX.AppCompat.Widget.SearchView;
namespace keepass2android
{
- public abstract class GroupBaseActivity : LockCloseActivity
+ public abstract class GroupBaseActivity : LockCloseActivity, IProgressUiProvider
{
public const String KeyEntry = "entry";
public const String KeyMode = "mode";
@@ -1408,6 +1409,14 @@ namespace keepass2android
{
GroupEditActivity.Launch(this, pwGroup.ParentGroup, pwGroup);
}
+
+ public IProgressUi ProgressUi
+ {
+ get
+ {
+ return FindViewById(Resource.Id.background_ops_container);
+ }
+ }
}
public class GroupListFragment : ListFragment, AbsListView.IMultiChoiceModeListener
diff --git a/src/keepass2android-app/LifecycleAwareActivity.cs b/src/keepass2android-app/LifecycleAwareActivity.cs
index b6578e53..a91b3a49 100644
--- a/src/keepass2android-app/LifecycleAwareActivity.cs
+++ b/src/keepass2android-app/LifecycleAwareActivity.cs
@@ -105,6 +105,7 @@ namespace keepass2android
protected override void OnStart()
{
BlockingOperationRunner.SetNewActiveActivity(this);
+ BackgroundOperationRunner.Instance.SetNewActiveActivity(this, App.Kp2a);
base.OnStart();
Kp2aLog.Log(ClassName + ".OnStart" + " " + ID);
}
diff --git a/src/keepass2android-app/Resources/layout/background_operation_container.xml b/src/keepass2android-app/Resources/layout/background_operation_container.xml
new file mode 100644
index 00000000..7d8082fc
--- /dev/null
+++ b/src/keepass2android-app/Resources/layout/background_operation_container.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/keepass2android-app/Resources/layout/entry_view.xml b/src/keepass2android-app/Resources/layout/entry_view.xml
index 4b5d4477..2fef6269 100644
--- a/src/keepass2android-app/Resources/layout/entry_view.xml
+++ b/src/keepass2android-app/Resources/layout/entry_view.xml
@@ -4,18 +4,35 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
-
+ />
{
if (!String.IsNullOrEmpty(message))
- App.Kp2a.ShowMessage(activity, message, MessageSeverity.Error);
+ App.Kp2a.ShowMessage(activity, message, success ? MessageSeverity.Info : MessageSeverity.Error);
// Tell the adapter to refresh it's list
- BaseAdapter adapter = (activity as GroupBaseActivity)?.ListAdapter;
- adapter?.NotifyDataSetChanged();
+ BaseAdapter adapter = (activity as GroupBaseActivity)?.ListAdapter;
+ new Handler(Looper.MainLooper).Post(() => adapter?.NotifyDataSetChanged());
if (App.Kp2a.CurrentDb?.OtpAuxFileIoc != null)
{
var task2 = new SyncOtpAuxFile(_activity, App.Kp2a.CurrentDb.OtpAuxFileIoc);
-
- //TODO new BackgroundOperationRunner(App.Kp2a, activity, task2).Run(true);
+
+ BackgroundOperationRunner.Instance.Run(_activity, App.Kp2a, task2);
}
});
@@ -82,8 +83,7 @@ namespace keepass2android
task = new CheckDatabaseForChanges(_activity, App.Kp2a, onOperationFinishedHandler);
}
- //TODO var backgroundTaskRunner = new BackgroundOperationRunner(App.Kp2a, _activity, task);
- //TODO backgroundTaskRunner.Run();
+ BackgroundOperationRunner.Instance.Run(_activity, App.Kp2a, task);
}
}
diff --git a/src/keepass2android-app/views/Kp2aShortHelpView.cs b/src/keepass2android-app/views/Kp2aShortHelpView.cs
index 438e7e95..c2961a5a 100644
--- a/src/keepass2android-app/views/Kp2aShortHelpView.cs
+++ b/src/keepass2android-app/views/Kp2aShortHelpView.cs
@@ -18,6 +18,7 @@ using Android.Views;
using Android.Widget;
using Google.Android.Material.Dialog;
using keepass2android;
+using KeePassLib.Interfaces;
namespace keepass2android.views
{
@@ -103,4 +104,92 @@ namespace keepass2android.views
}
}
+
+
+ public class BackgroundOperationContainer : LinearLayout, IProgressUi
+ {
+ protected BackgroundOperationContainer(IntPtr javaReference, JniHandleOwnership transfer) : base(
+ javaReference, transfer)
+ {
+ }
+
+ public BackgroundOperationContainer(Context context) : base(context)
+ {
+ }
+
+ public BackgroundOperationContainer(Context context, IAttributeSet attrs) : base(context, attrs)
+ {
+ Initialize(attrs);
+ }
+
+ public BackgroundOperationContainer(Context context, IAttributeSet attrs, int defStyle) : base(context,
+ attrs, defStyle)
+ {
+ Initialize(attrs);
+ }
+
+ private void Initialize(IAttributeSet attrs)
+ {
+
+ LayoutInflater inflater = (LayoutInflater)Context.GetSystemService(Context.LayoutInflaterService);
+ inflater.Inflate(Resource.Layout.background_operation_container, this);
+
+
+ }
+
+ public void Show()
+ {
+ new Handler(Looper.MainLooper).Post(() =>
+ {
+ Visibility = ViewStates.Visible;
+ FindViewById(Resource.Id.background_ops_message)!.Visibility = ViewStates.Gone;
+ FindViewById(Resource.Id.background_ops_submessage)!.Visibility = ViewStates.Gone;
+ });
+
+ }
+
+ public void Hide()
+ {
+ new Handler(Looper.MainLooper).Post(() =>
+ {
+ String activityType = Context.GetType().FullName;
+ Kp2aLog.Log("Hiding background ops container in" + activityType);
+ Visibility = ViewStates.Gone;
+ });
+ }
+
+ public void UpdateMessage(string message)
+ {
+ new Handler(Looper.MainLooper).Post(() =>
+ {
+ TextView messageTextView = FindViewById(Resource.Id.background_ops_message)!;
+ if (string.IsNullOrEmpty(message))
+ {
+ messageTextView.Visibility = ViewStates.Gone;
+ }
+ else
+ {
+ messageTextView.Visibility = ViewStates.Visible;
+ messageTextView.Text = message;
+ }
+ });
+ }
+
+ public void UpdateSubMessage(string submessage)
+ {
+ new Handler(Looper.MainLooper).Post(() =>
+ {
+ TextView subMessageTextView = FindViewById(Resource.Id.background_ops_submessage)!;
+ if (string.IsNullOrEmpty(submessage))
+ {
+ subMessageTextView.Visibility = ViewStates.Gone;
+ }
+ else
+ {
+ subMessageTextView.Visibility = ViewStates.Visible;
+ subMessageTextView.Text = submessage;
+ }
+ });
+ }
+ }
}
\ No newline at end of file