allow to cancel background operations manually or when another operation starts; block database updates in certain activities, e.g. EntryEdit;
This commit is contained in:
		| @@ -123,7 +123,15 @@ namespace keepass2android | ||||
|  | ||||
| 		protected override void OnCreate(Bundle savedInstanceState) | ||||
| 		{ | ||||
| 			base.OnCreate(savedInstanceState); | ||||
|             //we don't want any background thread to update/reload the database while we're in this activity. | ||||
|             if (!App.Kp2a.DatabasesBackgroundModificationLock.TryEnterReadLock(TimeSpan.FromSeconds(5))) | ||||
|             { | ||||
|                 App.Kp2a.ShowMessage(this, GetString(Resource.String.failed_to_access_database), MessageSeverity.Error); | ||||
|                 Finish(); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             base.OnCreate(savedInstanceState); | ||||
| 			 | ||||
| 			if (LastNonConfigurationInstance != null) | ||||
| 			{ | ||||
| @@ -329,11 +337,19 @@ namespace keepass2android | ||||
|              | ||||
|  | ||||
|  | ||||
|         } | ||||
|  | ||||
|         protected override void OnDestroy() | ||||
|         { | ||||
|             base.OnDestroy(); | ||||
|             //we don't want any background thread to update/reload the database while we're in this activity. | ||||
|             App.Kp2a.DatabasesBackgroundModificationLock.ExitReadLock(); | ||||
|         } | ||||
|  | ||||
|         private bool hasRequestedKeyboardActivation = false; | ||||
|         protected override void OnStart() | ||||
| 	    { | ||||
|         { | ||||
| 			 | ||||
| 	        base.OnStart(); | ||||
| 	        if (PreferenceManager.GetDefaultSharedPreferences(this) | ||||
| 	            .GetBoolean(GetString(Resource.String.UseKp2aKeyboardInKp2a_key), false) | ||||
|   | ||||
| @@ -14,8 +14,18 @@ | ||||
|       android:layout_width="match_parent" | ||||
|       android:layout_height="wrap_content" | ||||
|       android:indeterminate="true" /> | ||||
|          | ||||
|     <LinearLayout  | ||||
|                   android:layout_width="match_parent" | ||||
|                   android:layout_height="wrap_content" | ||||
|                   android:orientation="horizontal"    | ||||
|     > | ||||
|  | ||||
|       <LinearLayout  | ||||
|         android:layout_width="0dp" | ||||
|         android:layout_weight="1" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:orientation="vertical"    | ||||
|       > | ||||
|     <TextView  | ||||
|       android:id="@+id/background_ops_message" | ||||
|         android:layout_width="wrap_content" | ||||
| @@ -37,6 +47,19 @@ | ||||
|                 android:textSize="12sp" | ||||
|               android:text="" /> | ||||
|  | ||||
|            </LinearLayout> | ||||
|  | ||||
|       <Button | ||||
|         android:id="@+id/cancel_background" | ||||
|         style="?attr/materialIconButtonStyle" | ||||
|         app:icon="@drawable/baseline_close_24" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:padding="6dip" | ||||
|         android:layout_margin="6dip" | ||||
|         android:layout_weight="0"  | ||||
|       /> | ||||
|  | ||||
|         </LinearLayout> | ||||
|   </LinearLayout> | ||||
| 		 | ||||
| @@ -1256,4 +1256,5 @@ | ||||
|   <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> | ||||
|   <string name="failed_to_access_database">Database is currently in use and cannot be accessed.</string> | ||||
| </resources> | ||||
| @@ -78,7 +78,15 @@ namespace keepass2android | ||||
|  | ||||
|         protected override void OnCreate(Bundle savedInstanceState) | ||||
| 		{ | ||||
| 			base.OnCreate(savedInstanceState); | ||||
|             //we don't want any background thread to update/reload the database while we're in this activity. | ||||
|             if (!App.Kp2a.DatabasesBackgroundModificationLock.TryEnterReadLock(TimeSpan.FromSeconds(5))) | ||||
|             { | ||||
|                 App.Kp2a.ShowMessage(this, GetString(Resource.String.failed_to_access_database), MessageSeverity.Error); | ||||
|                 Finish(); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             base.OnCreate(savedInstanceState); | ||||
|  | ||||
| 			//if user presses back to leave this activity: | ||||
| 			SetResult(Result.Canceled); | ||||
| @@ -288,5 +296,12 @@ namespace keepass2android | ||||
| 	    { | ||||
| 	        get { return null; } | ||||
| 	    } | ||||
| 	}} | ||||
|  | ||||
|         protected override void OnDestroy() | ||||
|         { | ||||
|             base.OnDestroy(); | ||||
|             App.Kp2a.DatabasesBackgroundModificationLock.ExitReadLock(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -79,7 +79,7 @@ namespace keepass2android | ||||
|             if (filestorage is CachingFileStorage) | ||||
|             { | ||||
|  | ||||
|                 task = new SynchronizeCachedDatabase(App.Kp2a, onOperationFinishedHandler); | ||||
|                 task = new SynchronizeCachedDatabase(App.Kp2a, onOperationFinishedHandler, new BackgroundDatabaseModificationLocker(App.Kp2a)); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|   | ||||
| @@ -116,15 +116,12 @@ namespace keepass2android | ||||
| #endif | ||||
|  | ||||
|  | ||||
|  | ||||
| 	/// <summary> | ||||
| 	/// Main implementation of the IKp2aApp interface for usage in the real app. | ||||
| 	/// </summary> | ||||
| 	public class Kp2aApp: IKp2aApp, ICacheSupervisor | ||||
| 	{ | ||||
|  | ||||
|  | ||||
| 		public void Lock(bool allowQuickUnlock = true, bool lockWasTriggeredByTimeout = false) | ||||
|     { | ||||
|         public void Lock(bool allowQuickUnlock = true, bool lockWasTriggeredByTimeout = false) | ||||
| 	    { | ||||
| 			if (OpenDatabases.Any()) | ||||
| 			{ | ||||
| @@ -194,7 +191,9 @@ namespace keepass2android | ||||
|  | ||||
|  | ||||
|  | ||||
| 	    public Database LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, CompositeKey compositeKey, IKp2aStatusLogger statusLogger, IDatabaseFormat databaseFormat, bool makeCurrent) | ||||
| 	    public Database LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, | ||||
|             CompositeKey compositeKey, IKp2aStatusLogger statusLogger, IDatabaseFormat databaseFormat, bool makeCurrent, | ||||
|             IDatabaseModificationWatcher modificationWatcher) | ||||
| 	    { | ||||
|             | ||||
|  | ||||
| @@ -215,39 +214,50 @@ namespace keepass2android | ||||
|                 memoryStream.Seek(0, SeekOrigin.Begin); | ||||
|             } | ||||
|  | ||||
|  | ||||
|             if (!statusLogger.ContinueWork()) | ||||
|             { | ||||
|                 throw new Java.Lang.InterruptedException(); | ||||
|             } | ||||
|  | ||||
|             _openAttempts.Add(ioConnectionInfo); | ||||
|             var newDb = new Database(new DrawableFactory(), this); | ||||
|             newDb.LoadData(this, ioConnectionInfo, memoryStream, compositeKey, statusLogger, databaseFormat); | ||||
|  | ||||
|             modificationWatcher.BeforeModifyDatabases(); | ||||
|  | ||||
|  | ||||
|              | ||||
|             if ((_currentDatabase == null) || makeCurrent) _currentDatabase = newDb; | ||||
|  | ||||
|             bool replacedOpenDatabase = false; | ||||
|             for (int i = 0; i < _openDatabases.Count; i++) | ||||
|             try | ||||
|             { | ||||
|                 if (_openDatabases[i].Ioc.IsSameFileAs(ioConnectionInfo)) | ||||
|  | ||||
|  | ||||
|  | ||||
|                 if ((_currentDatabase == null) || makeCurrent) _currentDatabase = newDb; | ||||
|  | ||||
|                 bool replacedOpenDatabase = false; | ||||
|                 for (int i = 0; i < _openDatabases.Count; i++) | ||||
|                 { | ||||
|                     if (_currentDatabase == _openDatabases[i]) | ||||
|                     if (_openDatabases[i].Ioc.IsSameFileAs(ioConnectionInfo)) | ||||
|                     { | ||||
|                         _currentDatabase = newDb; | ||||
|                         if (_currentDatabase == _openDatabases[i]) | ||||
|                         { | ||||
|                             _currentDatabase = newDb; | ||||
|                         } | ||||
|  | ||||
|                         replacedOpenDatabase = true; | ||||
|                         _openDatabases[i] = newDb; | ||||
|  | ||||
|                         break; | ||||
|                     } | ||||
|  | ||||
|                     replacedOpenDatabase = true; | ||||
|                     _openDatabases[i] = newDb; | ||||
|  | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (!replacedOpenDatabase) | ||||
|                 if (!replacedOpenDatabase) | ||||
|                 { | ||||
|                     _openDatabases.Add(newDb); | ||||
|                 } | ||||
|  | ||||
|             } | ||||
|             finally | ||||
|             { | ||||
| 				modificationWatcher.AfterModifyDatabases(); | ||||
|             } | ||||
|  | ||||
|  | ||||
|             if (createBackup) | ||||
| @@ -302,9 +312,9 @@ namespace keepass2android | ||||
| 	        return newDb; | ||||
| 	    } | ||||
|  | ||||
| 	     | ||||
|          | ||||
|  | ||||
| 	    public void CloseDatabase(Database db) | ||||
|         public void CloseDatabase(Database db) | ||||
| 	    { | ||||
|              | ||||
|             if (!_openDatabases.Contains(db)) | ||||
| @@ -745,6 +755,11 @@ namespace keepass2android | ||||
|             EventHandler<DialogClickEventArgs> cancelHandler, | ||||
| 			EventHandler dismissHandler,string messageSuffix = "") | ||||
|         { | ||||
|             if (Java.Lang.Thread.Interrupted()) | ||||
|             { | ||||
|                 throw new Java.Lang.InterruptedException(); | ||||
|             } | ||||
|  | ||||
|             _currentlyPendingYesNoCancelQuestion = new YesNoCancelQuestion() | ||||
|             { | ||||
|                 TitleKey = titleKey, | ||||
| @@ -1472,10 +1487,38 @@ namespace keepass2android | ||||
|                 _currentlyPendingYesNoCancelQuestion?.TryShow(this, OnUserInputDialogClose, OnUserInputDialogClose); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// If the database is updated from a background operation, that operation needs to acquire a writer lock on this. | ||||
|         /// </summary> | ||||
|         /// Activities can acquire a reader lock if they want to make sure that no background operation is modifying the database while they are open. | ||||
|         public ReaderWriterLockSlim DatabasesBackgroundModificationLock { get; } = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); | ||||
|  | ||||
|         public bool CancelBackgroundOperations() | ||||
|         { | ||||
|             if (!DatabasesBackgroundModificationLock.TryEnterReadLock(TimeSpan.FromSeconds(5))) | ||||
|             { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             try | ||||
|             { | ||||
|                 BackgroundOperationRunner.Instance.CancelAll(); | ||||
| 				 | ||||
|             } | ||||
| 			finally | ||||
|             { | ||||
| 				DatabasesBackgroundModificationLock.ExitReadLock(); | ||||
|             } | ||||
|  | ||||
|             return true; | ||||
|              | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
| 	///Application class for Keepass2Android: Contains static Database variable to be used by all components. | ||||
|  | ||||
|     ///Application class for Keepass2Android: Contains static Database variable to be used by all components. | ||||
| #if NoNet | ||||
| 	[Application(Debuggable=false, Label=AppNames.AppName)] | ||||
| #else | ||||
|   | ||||
| @@ -48,7 +48,15 @@ namespace keepass2android.search | ||||
| 	{ | ||||
| 	    protected override void OnCreate (Bundle bundle) | ||||
| 		{ | ||||
| 			base.OnCreate (bundle); | ||||
|             //we don't want any background thread to update/reload the database while we're in this activity. We're showing a temporary group, so background updating doesn't work well. | ||||
|             if (!App.Kp2a.DatabasesBackgroundModificationLock.TryEnterReadLock(TimeSpan.FromSeconds(5))) | ||||
|             { | ||||
|                 App.Kp2a.ShowMessage(this, GetString(Resource.String.failed_to_access_database), MessageSeverity.Error); | ||||
|                 Finish(); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             base.OnCreate (bundle); | ||||
| 			 | ||||
| 			if ( IsFinishing ) { | ||||
| 				return; | ||||
| @@ -59,7 +67,13 @@ namespace keepass2android.search | ||||
| 			ProcessIntent(Intent); | ||||
| 		} | ||||
|  | ||||
| 	    public override bool EntriesBelongToCurrentDatabaseOnly | ||||
|         protected override void OnDestroy() | ||||
|         { | ||||
|             base.OnDestroy(); | ||||
| 			App.Kp2a.DatabasesBackgroundModificationLock.ExitReadLock(); | ||||
|         } | ||||
|  | ||||
|         public override bool EntriesBelongToCurrentDatabaseOnly | ||||
| 	    { | ||||
| 	        get { return false; } | ||||
| 	    } | ||||
|   | ||||
| @@ -0,0 +1,97 @@ | ||||
| using Android.Content; | ||||
| using Android.OS; | ||||
| using Android.Runtime; | ||||
| using Android.Util; | ||||
| using Android.Views; | ||||
|  | ||||
| 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); | ||||
|  | ||||
|         FindViewById(Resource.Id.cancel_background).Click += (obj,args) => | ||||
|         { | ||||
|             App.Kp2a.CancelBackgroundOperations(); | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     public void Show() | ||||
|     { | ||||
|         new Handler(Looper.MainLooper).Post(() => | ||||
|         { | ||||
|             Visibility = ViewStates.Visible; | ||||
|             FindViewById<TextView>(Resource.Id.background_ops_message)!.Visibility = ViewStates.Gone; | ||||
|             FindViewById<TextView>(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<TextView>(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<TextView>(Resource.Id.background_ops_submessage)!; | ||||
|             if (string.IsNullOrEmpty(submessage)) | ||||
|             { | ||||
|                 subMessageTextView.Visibility = ViewStates.Gone; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 subMessageTextView.Visibility = ViewStates.Visible; | ||||
|                 subMessageTextView.Text = submessage; | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| @@ -7,7 +7,6 @@ using Android.App; | ||||
| using Android.Content; | ||||
| using Android.Content.Res; | ||||
| using Android.Graphics; | ||||
| using Android.OS; | ||||
| using Android.Runtime; | ||||
| using Android.Text; | ||||
| using Android.Text.Method; | ||||
| @@ -104,92 +103,4 @@ 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<TextView>(Resource.Id.background_ops_message)!.Visibility = ViewStates.Gone; | ||||
|                 FindViewById<TextView>(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<TextView>(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<TextView>(Resource.Id.background_ops_submessage)!; | ||||
|                 if (string.IsNullOrEmpty(submessage)) | ||||
|                 { | ||||
|                     subMessageTextView.Visibility = ViewStates.Gone; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     subMessageTextView.Visibility = ViewStates.Visible; | ||||
|                     subMessageTextView.Text = submessage; | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Philipp Crocoll
					Philipp Crocoll