Merge branch 'master' into 2507--dialog-behind-keyboard
This commit is contained in:
		| @@ -91,7 +91,29 @@ namespace keepass2android | ||||
| 			 | ||||
| 		} | ||||
|  | ||||
| 		private static String ExtractHost(String url) | ||||
|         public PwGroup SearchForUuid(Database database, string uuid) | ||||
|         { | ||||
|             SearchParameters sp = SearchParameters.None; | ||||
|             sp.SearchInUuids = true; | ||||
|             sp.SearchString = uuid; | ||||
|  | ||||
|             if (sp.RegularExpression) // Validate regular expression | ||||
|             { | ||||
|                 new Regex(sp.SearchString); | ||||
|             } | ||||
|  | ||||
|             string strGroupName = _app.GetResourceString(UiStringKey.search_results); | ||||
|             PwGroup pgResults = new PwGroup(true, true, strGroupName, PwIcon.EMailSearch) { IsVirtual = true }; | ||||
|  | ||||
|             PwObjectList<PwEntry> listResults = pgResults.Entries; | ||||
|  | ||||
|             database.Root.SearchEntries(sp, listResults, new NullStatusLogger()); | ||||
|  | ||||
|             return pgResults; | ||||
|  | ||||
|         } | ||||
|  | ||||
|         private static String ExtractHost(String url) | ||||
| 		{ | ||||
| 			return UrlUtil.GetHost(url.Trim()); | ||||
| 		} | ||||
|   | ||||
| @@ -174,10 +174,17 @@ namespace keepass2android | ||||
| 			PwGroup group = SearchHelper.SearchForExactUrl(this, url); | ||||
| 			 | ||||
| 			return group; | ||||
| 			 | ||||
| 		} | ||||
|  | ||||
| 		public PwGroup SearchForHost(String url, bool allowSubdomains) { | ||||
|         } | ||||
|         public PwGroup SearchForUuid(String uuid) | ||||
|         { | ||||
|             PwGroup group = SearchHelper.SearchForUuid(this, uuid); | ||||
|  | ||||
|             return group; | ||||
|  | ||||
|         } | ||||
|  | ||||
|         public PwGroup SearchForHost(String url, bool allowSubdomains) { | ||||
| 			PwGroup group = SearchHelper.SearchForHost(this, url, allowSubdomains); | ||||
| 			 | ||||
| 			return group; | ||||
|   | ||||
| @@ -32,6 +32,7 @@ using Android.Text.Method; | ||||
| using System.Globalization; | ||||
| using System.IO; | ||||
| using System.Net; | ||||
| using System.Threading.Tasks; | ||||
| using Android.Content.PM; | ||||
| using Android.Webkit; | ||||
| using Android.Graphics; | ||||
| @@ -49,7 +50,9 @@ using PluginTOTP; | ||||
| using File = Java.IO.File; | ||||
| using Uri = Android.Net.Uri; | ||||
| using keepass2android.fileselect; | ||||
| using KeeTrayTOTP.Libraries; | ||||
| using Boolean = Java.Lang.Boolean; | ||||
| using Android.Util; | ||||
|  | ||||
| namespace keepass2android | ||||
| { | ||||
| @@ -286,6 +289,8 @@ namespace keepass2android | ||||
| 				extraGroup.AddView(view.View); | ||||
| 			} | ||||
|  | ||||
|             SetPasswordStyle(); | ||||
|  | ||||
| 			//update the Entry output in the App database and notify the CopyToClipboard service | ||||
|  | ||||
| 		    if (App.Kp2a.LastOpenedEntry != null) | ||||
| @@ -488,10 +493,11 @@ namespace keepass2android | ||||
| 			_pluginFieldReceiver = new PluginFieldReceiver(this); | ||||
| 			RegisterReceiver(_pluginFieldReceiver, new IntentFilter(Strings.ActionSetEntryField)); | ||||
|  | ||||
| 			new Thread(NotifyPluginsOnOpen).Start(); | ||||
|             var notifyPluginsOnOpenThread = new Thread(NotifyPluginsOnOpen); | ||||
|             notifyPluginsOnOpenThread.Start(); | ||||
|  | ||||
| 			//the rest of the things to do depends on the current app task: | ||||
| 			AppTask.CompleteOnCreateEntryActivity(this); | ||||
| 			AppTask.CompleteOnCreateEntryActivity(this, notifyPluginsOnOpenThread); | ||||
| 		} | ||||
|  | ||||
|         private void RemoveFromHistory() | ||||
| @@ -664,7 +670,7 @@ namespace keepass2android | ||||
| 			EditModeBase editMode = new DefaultEdit(); | ||||
| 			if (KpEntryTemplatedEdit.IsTemplated(App.Kp2a.CurrentDb, this.Entry)) | ||||
| 				editMode = new KpEntryTemplatedEdit(App.Kp2a.CurrentDb, this.Entry); | ||||
| 			foreach (var key in  editMode.SortExtraFieldKeys(Entry.Strings.GetKeys().Where(key=> !PwDefs.IsStandardField(key)))) | ||||
| 			foreach (var key in  editMode.SortExtraFieldKeys(Entry.Strings.GetKeys().Where(key=> !PwDefs.IsStandardField(key) && key != Kp2aTotp.TotpKey))) | ||||
| 			{ | ||||
| 				if (editMode.IsVisible(key)) | ||||
| 				{ | ||||
| @@ -840,7 +846,7 @@ namespace keepass2android | ||||
| 		{ | ||||
|             if (!_showPassword.ContainsKey(protectedTextView)) | ||||
|             { | ||||
|                 _showPassword[protectedTextView] = fieldKey == UpdateTotpTimerTask.TotpKey ? _showTotpDefault : _showPasswordDefault; | ||||
|                 _showPassword[protectedTextView] = fieldKey == Kp2aTotp.TotpKey ? _showTotpDefault : _showPasswordDefault; | ||||
|             } | ||||
| 		    var protectedTextviewGroup = new ProtectedTextviewGroup { ProtectedField = protectedTextView, VisibleProtectedField = visibleTextView}; | ||||
| 		    _protectedTextViews.Add(protectedTextviewGroup); | ||||
| @@ -946,11 +952,13 @@ namespace keepass2android | ||||
|  | ||||
| 			PopulateStandardText(Resource.Id.entry_user_name, Resource.Id.entryfield_container_username, PwDefs.UserNameField); | ||||
| 			PopulateStandardText(Resource.Id.entry_url, Resource.Id.entryfield_container_url, PwDefs.UrlField); | ||||
| 			PopulateStandardText(new List<int> { Resource.Id.entry_password, Resource.Id.entry_password_visible}, Resource.Id.entryfield_container_password, PwDefs.PasswordField); | ||||
|             PopulateStandardText(new List<int> { Resource.Id.entry_totp, Resource.Id.entry_totp_visible }, Resource.Id.entryfield_container_totp, Kp2aTotp.TotpKey); | ||||
|             PopulateStandardText(new List<int> { Resource.Id.entry_password, Resource.Id.entry_password_visible}, Resource.Id.entryfield_container_password, PwDefs.PasswordField); | ||||
| 		     | ||||
|             RegisterProtectedTextView(PwDefs.PasswordField, FindViewById<TextView>(Resource.Id.entry_password), FindViewById<TextView>(Resource.Id.entry_password_visible)); | ||||
|             RegisterProtectedTextView(Kp2aTotp.TotpKey, FindViewById<TextView>(Resource.Id.entry_totp), FindViewById<TextView>(Resource.Id.entry_totp_visible)); | ||||
|  | ||||
| 			RegisterTextPopup(FindViewById<RelativeLayout> (Resource.Id.groupname_container), | ||||
|             RegisterTextPopup(FindViewById<RelativeLayout> (Resource.Id.groupname_container), | ||||
| 				              FindViewById (Resource.Id.entry_group_name), KeyGroupFullPath); | ||||
|  | ||||
| 			RegisterTextPopup(FindViewById<RelativeLayout>(Resource.Id.username_container), | ||||
| @@ -961,9 +969,11 @@ namespace keepass2android | ||||
| 				.Add(new GotoUrlMenuItem(this, PwDefs.UrlField)); | ||||
| 			RegisterTextPopup(FindViewById<RelativeLayout>(Resource.Id.password_container), | ||||
| 			                  FindViewById(Resource.Id.password_vdots), PwDefs.PasswordField); | ||||
|             RegisterTextPopup(FindViewById<RelativeLayout>(Resource.Id.totp_container), | ||||
|                 FindViewById(Resource.Id.totp_vdots), Kp2aTotp.TotpKey); | ||||
|  | ||||
|  | ||||
| 			PopulateText(Resource.Id.entry_created, Resource.Id.entryfield_container_created, getDateTime(Entry.CreationTime)); | ||||
|             PopulateText(Resource.Id.entry_created, Resource.Id.entryfield_container_created, getDateTime(Entry.CreationTime)); | ||||
| 			PopulateText(Resource.Id.entry_modified, Resource.Id.entryfield_container_modified, getDateTime(Entry.LastModificationTime)); | ||||
|  | ||||
| 			if (Entry.Expires) | ||||
| @@ -990,6 +1000,40 @@ namespace keepass2android | ||||
|  | ||||
| 			SetPasswordStyle(); | ||||
| 		} | ||||
| 		 | ||||
|         private async Task UpdateTotpCountdown() | ||||
|         { | ||||
|             if (App.Kp2a.LastOpenedEntry == null) | ||||
|                 return; | ||||
|             var totpData = new Kp2aTotp().TryGetTotpData(App.Kp2a.LastOpenedEntry); | ||||
|  | ||||
|             if (totpData == null || !totpData.IsTotpEntry) | ||||
|                 return; | ||||
|  | ||||
|             var totpProvider = new TOTPProvider(totpData); | ||||
|  | ||||
|             var progressBar = FindViewById<ProgressBar>(Resource.Id.TotpCountdownProgressBar); | ||||
|  | ||||
|             int lastSecondsLeft = -1; | ||||
|             while (!isPaused && progressBar != null) | ||||
|             { | ||||
|  | ||||
|                 int secondsLeft = totpProvider.Timer; | ||||
|  | ||||
|                 if (secondsLeft != lastSecondsLeft) | ||||
|                 { | ||||
|                     lastSecondsLeft = secondsLeft; | ||||
|                     // Update the progress bar on the UI thread | ||||
|                     RunOnUiThread(() => | ||||
|                     { | ||||
|                         progressBar.Progress = secondsLeft; | ||||
|                         progressBar.Max = totpProvider.Duration; | ||||
|                     }); | ||||
|                 } | ||||
|                  | ||||
| 				await Task.Delay(1000);  | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private void PopulatePreviousVersions() | ||||
|         { | ||||
| @@ -1042,7 +1086,7 @@ namespace keepass2android | ||||
| 		} | ||||
| 		private List<IPopupMenuItem> RegisterTextPopup(View container, View anchor, string fieldKey) | ||||
| 		{ | ||||
| 			return RegisterTextPopup(container, anchor, fieldKey, Entry.Strings.GetSafe(fieldKey).IsProtected); | ||||
| 			return RegisterTextPopup(container, anchor, fieldKey, Entry.Strings.GetSafe(fieldKey).IsProtected || fieldKey == Kp2aTotp.TotpKey); | ||||
| 		} | ||||
|  | ||||
| 		private List<IPopupMenuItem> RegisterTextPopup(View container, View anchor, string fieldKey, bool isProtected) | ||||
| @@ -1055,7 +1099,12 @@ namespace keepass2android | ||||
| 			popupItems.Add(new CopyToClipboardPopupMenuIcon(this, _stringViews[fieldKey], isProtected)); | ||||
|             if (isProtected) | ||||
|             { | ||||
|                 var valueView = container.FindViewById<TextView>(fieldKey == PwDefs.PasswordField ? Resource.Id.entry_password : Resource.Id.entry_extra); | ||||
|                 var valueView = container.FindViewById<TextView>(fieldKey switch | ||||
|                 { | ||||
|                     PwDefs.PasswordField => Resource.Id.entry_password, | ||||
|                     Kp2aTotp.TotpKey => Resource.Id.entry_totp, | ||||
|                     _ => Resource.Id.entry_extra | ||||
|                 }); | ||||
| 				popupItems.Add(new ToggleVisibilityPopupMenuItem(this, valueView)); | ||||
|             } | ||||
|  | ||||
| @@ -1282,11 +1331,16 @@ namespace keepass2android | ||||
| 			return base.OnPrepareOptionsMenu(menu); | ||||
| 		} | ||||
|  | ||||
|          | ||||
| 		bool isPaused = false; | ||||
|  | ||||
| 		 | ||||
|         protected override void OnPause() | ||||
|         { | ||||
|             base.OnPause(); | ||||
| 			isPaused = true; | ||||
|         } | ||||
|  | ||||
| 		private void UpdateTogglePasswordMenu() | ||||
|  | ||||
|         private void UpdateTogglePasswordMenu() | ||||
| 		{ | ||||
| 			IMenuItem togglePassword = _menu.FindItem(Resource.Id.menu_toggle_pass); | ||||
| 			if (_showPassword.Values.All(x => x)) | ||||
| @@ -1323,7 +1377,9 @@ namespace keepass2android | ||||
| 			ClearCache(); | ||||
| 			base.OnResume(); | ||||
| 			_activityDesign.ReapplyTheme(); | ||||
| 		} | ||||
|             isPaused = false; | ||||
|             Task.Run(UpdateTotpCountdown); | ||||
|         } | ||||
|  | ||||
| 		public void ClearCache() | ||||
| 		{ | ||||
|   | ||||
| @@ -140,7 +140,7 @@ namespace keepass2android | ||||
|             //will return the results later | ||||
|             Intent i = new Intent(this, typeof (SelectCurrentDbActivity)); | ||||
| 			//don't show user notifications when an entry is opened. | ||||
| 			var task = new SearchUrlTask() {UrlToSearchFor = _requestedUrl, ShowUserNotifications = ShowUserNotificationsMode.WhenTotp}; | ||||
| 			var task = new SearchUrlTask() {UrlToSearchFor = _requestedUrl, ShowUserNotifications = ActivationCondition.WhenTotp, ActivateKeyboard = ActivationCondition.Never }; | ||||
| 			task.ToIntent(i); | ||||
| 			StartActivityForResult(i, RequestCodeQuery); | ||||
| 			_startedQuery = true; | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								src/keepass2android/Resources/drawable-mdpi/ic_entry_totp.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/keepass2android/Resources/drawable-mdpi/ic_entry_totp.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 5.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/keepass2android/Resources/drawable-xhdpi/ic_entry_totp.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/keepass2android/Resources/drawable-xhdpi/ic_entry_totp.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 7.1 KiB | 
| @@ -184,6 +184,68 @@ | ||||
|       </RelativeLayout> | ||||
|     </LinearLayout> | ||||
|   </LinearLayout> | ||||
|   <LinearLayout | ||||
|     android:id="@+id/entryfield_container_totp" | ||||
|     style="@style/EntryEditSingleLine_container"> | ||||
|     <ImageView | ||||
|       style="@style/EntryEditSingleLine_ImageView" | ||||
|       android:src="@drawable/ic_entry_totp" /> | ||||
|  | ||||
|     <LinearLayout | ||||
|          | ||||
|       android:layout_height="wrap_content" | ||||
|       android:layout_width="fill_parent" | ||||
|       android:orientation="vertical"> | ||||
|       <!-- TOTP --> | ||||
|       <TextView | ||||
|         android:id="@+id/entry_totp_label" | ||||
|         android:layout_width="fill_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:text="@string/TOTP" | ||||
|         style="@style/EntryFieldHeader" /> | ||||
|       <RelativeLayout | ||||
|         android:id="@+id/totp_container" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_width="fill_parent" | ||||
|         android:orientation="horizontal" | ||||
|         android:clickable="true" | ||||
|         android:background="?android:attr/selectableItemBackground"> | ||||
|         <ImageView | ||||
|           android:id="@+id/totp_vdots" | ||||
|           android:layout_width="wrap_content" | ||||
|           android:layout_height="15dp" | ||||
|           android:src="@drawable/vdots" | ||||
|           android:gravity="right|bottom" | ||||
|           android:layout_alignParentRight="true" /> | ||||
|         <TextView | ||||
|           android:id="@+id/entry_totp" | ||||
|           android:layout_width="fill_parent" | ||||
|           android:layout_height="wrap_content" | ||||
|           android:password="true" | ||||
|           android:typeface="monospace" | ||||
|           android:layout_toLeftOf="@id/totp_vdots" | ||||
|           style="@style/EntryItem" /> | ||||
|         <TextView | ||||
|           android:id="@+id/entry_totp_visible" | ||||
|           android:layout_width="fill_parent" | ||||
|           android:layout_height="wrap_content" | ||||
|           android:layout_toLeftOf="@id/totp_vdots" | ||||
|           style="@style/EntryItem" /> | ||||
|       </RelativeLayout> | ||||
|  | ||||
|        | ||||
|         <ProgressBar | ||||
|           android:id="@+id/TotpCountdownProgressBar" | ||||
|           style="?android:attr/progressBarStyleHorizontal" | ||||
|           android:layout_width="match_parent" | ||||
|           android:layout_height="wrap_content" | ||||
|           android:layout_alignParentTop="true" | ||||
|           android:layout_alignParentBottom="true" | ||||
|           android:layout_marginRight="30dp" /> | ||||
|  | ||||
|     </LinearLayout> | ||||
|   </LinearLayout> | ||||
|  | ||||
|   <LinearLayout | ||||
|     android:id="@+id/entryfield_container_comment" | ||||
|             style="@style/EntryEditSingleLine_container"> | ||||
|   | ||||
| @@ -47,6 +47,7 @@ | ||||
|     <string name="oi_filemanager_web">https://openintents.googlecode.com/files/FileManager-2.0.2.apk</string> | ||||
|   <string name="permission_desc2">KP2A Search</string> | ||||
|   <string name="permission_desc3">KP2A Choose autofill dataset</string> | ||||
|   <string name="AutoFillTotp_prefs_screen_key">AutoFillTotp_prefs_screen_key</string> | ||||
|    | ||||
|  | ||||
|   <!--  Preference settings --> | ||||
|   | ||||
| @@ -402,6 +402,16 @@ | ||||
|   <string name="ShowSeparateNotifications_summary">Show separate notifications for copying username and password to clipboard and activating the keyboard.</string> | ||||
|   <string name="AccServiceAutoFill_prefs">AutoFill Accessibility-Service</string> | ||||
|   <string name="AutoFill_prefs">AutoFill Service</string> | ||||
|   <string name="AutoFillTotp_prefs_ShowNotification_summary">When autofilling an entry with TOTP, show the entry notification with a Copy TOTP button</string> | ||||
|   <string name="AutoFillTotp_prefs_ShowNotification_title">Show entry notification</string> | ||||
|   <string name="AutoFillTotp_prefs_title">Autofill for TOTP entries</string> | ||||
|   <string name="AutoFillTotp_prefs_CopyTotpToClipboard_title">Copy TOTP to clipboard</string> | ||||
|   <string name="AutoFillTotp_prefs_CopyTotpToClipboard_summary">When autofilling an entry with TOTP, copy the TOTP to the clipboard</string> | ||||
|   <string name="AutoFillTotp_prefs_ActivateKeyboard_summary">When autofilling an entry with TOTP, activate the built-in keyboard. The keyboard has a TOTP button.</string> | ||||
|   <string name="AutoFillTotp_prefs_ActivateKeyboard_title">Activate built-in keyboard</string> | ||||
|    | ||||
|   <string name="TotpCopiedToClipboard">Copied TOTP to clipboard</string> | ||||
|  | ||||
|   <string name="ShowKp2aKeyboardNotification_title">KP2A keyboard notification</string> | ||||
|   <string name="ShowKp2aKeyboardNotification_summary">Make full entry accessible through the KP2A keyboard (recommended).</string> | ||||
|   <string name="OpenKp2aKeyboardAutomatically_title">Switch keyboard</string> | ||||
| @@ -589,6 +599,7 @@ | ||||
|   <string name="CouldntLoadChalAuxFile_Hint">Please use the KeeChallenge plugin in KeePass 2.x (PC) to configure your database for use with challenge-response!</string> | ||||
|   <string name="ErrorUpdatingChalAuxFile">Error updating OTP auxiliary file!</string> | ||||
|   <string name="TrayTotp_SeedField_title">TOTP Seed field name</string> | ||||
|   <string name="TOTP">TOTP</string> | ||||
|   <string name="TrayTotp_SeedField_summary">If you are using the Keepass 2 plugin "TrayTotp" with non-default settings, enter the field name for the seed field here according to the settings on the PC.</string> | ||||
|   <string name="TrayTotp_SettingsField_title">TOTP Settings field name</string> | ||||
|   <string name="TrayTotp_SettingsField_summary">Enter the field name of the settings field for TrayTotp here.</string> | ||||
|   | ||||
| @@ -461,7 +461,8 @@ | ||||
|           android:defaultValue="false" | ||||
|           android:title="@string/LogAutofillView_title" | ||||
|           android:key="@string/LogAutofillView_key" /> | ||||
|          | ||||
|  | ||||
|      | ||||
|  | ||||
|         <CheckBoxPreference | ||||
|           android:enabled="true" | ||||
| @@ -476,6 +477,38 @@ | ||||
| 		    android:summary="@string/AutofillDisabledQueriesPreference_summary" | ||||
| 		    android:persistent="false" | ||||
| 		    android:key="AutofillDisabledQueriesPreference_key"/> | ||||
|  | ||||
|         <PreferenceScreen | ||||
|           android:key="@string/AutoFillTotp_prefs_screen_key" | ||||
|           android:title="@string/AutoFillTotp_prefs_title" | ||||
|         > | ||||
|           <keepass2android.ToolbarPreference | ||||
|             android:key="@string/AutoFillTotp_prefs_screen_key" | ||||
|             android:title="@string/AutoFillTotp_prefs_title" /> | ||||
|  | ||||
| 					<CheckBoxPreference android:key="AutoFillTotp_prefs_ShowNotification_key" | ||||
|                               android:enabled="true" | ||||
|                               android:persistent="true" | ||||
|                               android:summary="@string/AutoFillTotp_prefs_ShowNotification_summary" | ||||
|                               android:defaultValue="true" | ||||
|                               android:title="@string/AutoFillTotp_prefs_ShowNotification_title" | ||||
|                               /> | ||||
|           <CheckBoxPreference android:key="AutoFillTotp_prefs_CopyTotpToClipboard_key" | ||||
|                               android:enabled="true" | ||||
|                               android:persistent="true" | ||||
|                               android:summary="@string/AutoFillTotp_prefs_CopyTotpToClipboard_summary" | ||||
|                               android:defaultValue="true" | ||||
|                               android:title="@string/AutoFillTotp_prefs_CopyTotpToClipboard_title" | ||||
|           /> | ||||
|           <CheckBoxPreference android:key="AutoFillTotp_prefs_ActivateKeyboard_key" | ||||
|                               android:enabled="true" | ||||
|                               android:persistent="true" | ||||
|                               android:summary="@string/AutoFillTotp_prefs_ActivateKeyboard_summary" | ||||
|                               android:defaultValue="false" | ||||
|                               android:title="@string/AutoFillTotp_prefs_ActivateKeyboard_title" | ||||
|           /> | ||||
|  | ||||
|         </PreferenceScreen> | ||||
|          | ||||
|       </PreferenceScreen> | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Diagnostics; | ||||
| using System.Linq; | ||||
| using System.Text; | ||||
|  | ||||
| @@ -306,7 +307,27 @@ namespace keepass2android | ||||
|                 } | ||||
|                 else if (Intent.Action == Intent.ActionSend) | ||||
|                 { | ||||
|                     AppTask = new SearchUrlTask { UrlToSearchFor = Intent.GetStringExtra(Intent.ExtraText) }; | ||||
|                     ActivationCondition activationCondition = ActivationCondition.Never; | ||||
|                     ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(this); | ||||
|                     if (prefs.GetBoolean("kp2a_switch_rooted", false)) | ||||
|                     { | ||||
|                         activationCondition = ActivationCondition.Always; | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         //if the app is about to be closed again (e.g. after searching for a URL and returning to the browser: | ||||
|                         // automatically bring up the Keyboard selection dialog | ||||
|                         if (prefs.GetBoolean(this.GetString(Resource.String.OpenKp2aKeyboardAutomatically_key), this.Resources.GetBoolean(Resource.Boolean.OpenKp2aKeyboardAutomatically_default))) | ||||
|                         { | ||||
|                             activationCondition = ActivationCondition.Always; | ||||
|                         } | ||||
|                     } | ||||
|                      | ||||
|                     AppTask = new SearchUrlTask() | ||||
|                     { | ||||
|                         UrlToSearchFor = Intent.GetStringExtra(Intent.ExtraText), | ||||
|                         ActivateKeyboard = activationCondition | ||||
|                     }; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|   | ||||
| @@ -63,6 +63,12 @@ namespace keepass2android | ||||
| 		    launchMode.Launch(act, i); | ||||
| 		} | ||||
|  | ||||
|         public static void Launch(Activity act, OpenSpecificEntryTask task, ActivityLaunchMode launchMode) | ||||
|         { | ||||
|             Intent i = new Intent(act, typeof(ShareUrlResults)); | ||||
|             task.ToIntent(i); | ||||
|             launchMode.Launch(act, i); | ||||
|         } | ||||
|  | ||||
|         public override bool IsSearchResult | ||||
|         { | ||||
| @@ -76,21 +82,15 @@ namespace keepass2android | ||||
| 			//if user presses back to leave this activity: | ||||
| 			SetResult(Result.Canceled); | ||||
|              | ||||
|  | ||||
| 		    UpdateBottomBarElementVisibility(Resource.Id.select_other_entry, true); | ||||
| 		    UpdateBottomBarElementVisibility(Resource.Id.add_url_entry, true); | ||||
|  | ||||
|  | ||||
|             if (App.Kp2a.DatabaseIsUnlocked) | ||||
| 			{ | ||||
| 			    var searchUrlTask = ((SearchUrlTask)AppTask); | ||||
| 			    String searchUrl = searchUrlTask.UrlToSearchFor; | ||||
| 				Query(searchUrl, searchUrlTask.AutoReturnFromQuery);	 | ||||
|                 Query(); | ||||
| 			} | ||||
|             // else: LockCloseListActivity.OnResume will trigger a broadcast (LockDatabase) which will cause the activity to be finished. | ||||
|  | ||||
|  | ||||
|  | ||||
|              | ||||
|         } | ||||
|  | ||||
|         protected override void OnSaveInstanceState(Bundle outState) | ||||
| @@ -99,12 +99,25 @@ namespace keepass2android | ||||
| 			AppTask.ToBundle(outState); | ||||
| 		} | ||||
|  | ||||
| 		private void Query(string url, bool autoReturnFromQuery) | ||||
| 		private void Query() | ||||
|         { | ||||
|              | ||||
|             bool canAutoReturnFromQuery = true; | ||||
|             bool shouldAutoReturnFromQuery = true; | ||||
|             try | ||||
|             { | ||||
|                 Group = GetSearchResultsForUrl(url); | ||||
| 				if (AppTask is SearchUrlTask searchUrlTask) | ||||
|                 { | ||||
|                     String searchUrl = searchUrlTask.UrlToSearchFor; | ||||
|                     canAutoReturnFromQuery = searchUrlTask.AutoReturnFromQuery; | ||||
|                     shouldAutoReturnFromQuery = PreferenceManager.GetDefaultSharedPreferences(this) | ||||
|                         .GetBoolean(GetString(Resource.String.AutoReturnFromQuery_key), true); | ||||
|                     Group = GetSearchResultsForUrl(searchUrl); | ||||
|                 } | ||||
| 				else if (AppTask is OpenSpecificEntryTask openEntryTask) | ||||
|                 { | ||||
|                     Group = GetSearchResultsForUuid(openEntryTask.EntryUuid); | ||||
|                 } | ||||
|                  | ||||
|             } catch (Exception e) | ||||
| 			{ | ||||
| 				Toast.MakeText(this, e.Message, ToastLength.Long).Show(); | ||||
| @@ -114,7 +127,7 @@ namespace keepass2android | ||||
| 			} | ||||
|  | ||||
| 			//if there is exactly one match: open the entry | ||||
| 			if ((Group.Entries.Count() == 1) && autoReturnFromQuery && PreferenceManager.GetDefaultSharedPreferences(this).GetBoolean(GetString(Resource.String.AutoReturnFromQuery_key),true)) | ||||
| 			if ((Group.Entries.Count() == 1) && canAutoReturnFromQuery && shouldAutoReturnFromQuery) | ||||
| 			{ | ||||
| 				LaunchActivityForEntry(Group.Entries.Single(),0); | ||||
| 				return; | ||||
| @@ -131,32 +144,57 @@ namespace keepass2android | ||||
|             FragmentManager.FindFragmentById<GroupListFragment>(Resource.Id.list_fragment).ListAdapter = new PwGroupListAdapter(this, Group); | ||||
|  | ||||
| 			View selectOtherEntry = FindViewById (Resource.Id.select_other_entry); | ||||
|             View createUrlEntry = FindViewById(Resource.Id.add_url_entry); | ||||
|  | ||||
|             var newTask = new SearchUrlTask() {AutoReturnFromQuery = false, UrlToSearchFor = url}; | ||||
| 		    if (AppTask is SelectEntryTask currentSelectTask) | ||||
| 		        newTask.ShowUserNotifications = currentSelectTask.ShowUserNotifications; | ||||
|              | ||||
|             selectOtherEntry.Click += (sender, e) => { | ||||
| 				GroupActivity.Launch (this, newTask, new ActivityLaunchModeRequestCode(0)); | ||||
|             if (AppTask is OpenSpecificEntryTask) | ||||
|             { | ||||
|                 selectOtherEntry.Visibility =  ViewStates.Gone; | ||||
|                 createUrlEntry.Visibility = ViewStates.Gone; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 var searchUrlTask = AppTask as SearchUrlTask; | ||||
|                 String searchUrl = searchUrlTask.UrlToSearchFor; | ||||
|                 selectOtherEntry.Visibility =  ViewStates.Visible; | ||||
|  | ||||
|                 SearchUrlTask newTask; | ||||
|                 if (AppTask is SelectEntryTask currentSelectTask) | ||||
|                 { | ||||
|                     newTask = new SearchUrlTask() { AutoReturnFromQuery = false, UrlToSearchFor = searchUrl, ActivateKeyboard = currentSelectTask.ActivateKeyboard }; | ||||
|                     newTask.ShowUserNotifications = currentSelectTask.ShowUserNotifications; | ||||
|                     newTask.ActivateKeyboard = currentSelectTask.ActivateKeyboard;   | ||||
|                     newTask.CopyTotpToClipboard = currentSelectTask.CopyTotpToClipboard; | ||||
|                 } | ||||
|                 else | ||||
|                     newTask = new SearchUrlTask() { AutoReturnFromQuery = false, UrlToSearchFor = searchUrl, ActivateKeyboard = ActivationCondition.Never }; | ||||
|  | ||||
|  | ||||
|                 selectOtherEntry.Click += (sender, e) => { | ||||
|                     GroupActivity.Launch(this, newTask, new ActivityLaunchModeRequestCode(0)); | ||||
|  | ||||
|                 }; | ||||
|  | ||||
|  | ||||
|                  | ||||
|  | ||||
|                 if (App.Kp2a.OpenDatabases.Any(db => db.CanWrite)) | ||||
|                 { | ||||
|                     createUrlEntry.Visibility = ViewStates.Visible; | ||||
|                     createUrlEntry.Click += (sender, e) => | ||||
|                     { | ||||
|                         GroupActivity.Launch(this, new CreateEntryThenCloseTask { Url = searchUrl, ShowUserNotifications = (AppTask as SelectEntryTask)?.ShowUserNotifications ?? ActivationCondition.Always }, new ActivityLaunchModeRequestCode(0)); | ||||
|                         Toast.MakeText(this, GetString(Resource.String.select_group_then_add, new Java.Lang.Object[] { GetString(Resource.String.add_entry) }), ToastLength.Long).Show(); | ||||
|                     }; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     createUrlEntry.Visibility = ViewStates.Gone; | ||||
|                 } | ||||
|  | ||||
|             } | ||||
|  | ||||
| 			}; | ||||
|  | ||||
| 			 | ||||
| 			View createUrlEntry = FindViewById (Resource.Id.add_url_entry); | ||||
|  | ||||
| 			if (App.Kp2a.OpenDatabases.Any(db => db.CanWrite)) | ||||
| 			{ | ||||
| 				createUrlEntry.Visibility = ViewStates.Visible; | ||||
| 				createUrlEntry.Click += (sender, e) => | ||||
| 				{ | ||||
| 					GroupActivity.Launch(this, new CreateEntryThenCloseTask { Url = url, ShowUserNotifications = (AppTask as SelectEntryTask)?.ShowUserNotifications ?? ShowUserNotificationsMode.Always }, new ActivityLaunchModeRequestCode(0)); | ||||
| 					Toast.MakeText(this, GetString(Resource.String.select_group_then_add, new Java.Lang.Object[] { GetString(Resource.String.add_entry) }), ToastLength.Long).Show(); | ||||
| 				}; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				createUrlEntry.Visibility = ViewStates.Gone; | ||||
| 			} | ||||
|  | ||||
| 			Util.MoveBottomBarButtons(Resource.Id.select_other_entry, Resource.Id.add_url_entry, Resource.Id.bottom_bar, this); | ||||
| 		} | ||||
| @@ -201,6 +239,31 @@ namespace keepass2android | ||||
|             return resultsGroup; | ||||
|         } | ||||
|  | ||||
|  | ||||
|         public static PwGroup GetSearchResultsForUuid(string uuid) | ||||
|         { | ||||
|             PwGroup resultsGroup = null; | ||||
|             foreach (var db in App.Kp2a.OpenDatabases) | ||||
|             { | ||||
|                  | ||||
|                 var resultsForThisDb = db.SearchForUuid(uuid); | ||||
|                  | ||||
|                 if (resultsGroup == null) | ||||
|                 { | ||||
|                     resultsGroup = resultsForThisDb; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     foreach (var entry in resultsForThisDb.Entries) | ||||
|                     { | ||||
|                         resultsGroup.AddEntry(entry, false, false); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return resultsGroup; | ||||
|         } | ||||
|  | ||||
|         public override bool OnSearchRequested() | ||||
| 		{ | ||||
| 			Intent i = new Intent(this, typeof(SearchActivity)); | ||||
|   | ||||
| @@ -2,6 +2,7 @@ using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Collections.Specialized; | ||||
| using Android.Content; | ||||
| using keepass2android; | ||||
| using KeePassLib.Collections; | ||||
|  | ||||
| namespace PluginTOTP | ||||
| @@ -39,23 +40,34 @@ namespace PluginTOTP | ||||
| 			{ | ||||
| 				TotpData res = new TotpData(); | ||||
| 				string data; | ||||
| 				if (!_entryFields.TryGetValue("otp", out data)) | ||||
|                 var otpKey = "otp"; | ||||
|                 if (!_entryFields.TryGetValue(otpKey, out data)) | ||||
| 				{ | ||||
| 					return res; | ||||
| 				} | ||||
| 				NameValueCollection parameters = ParseQueryString(data); | ||||
| 				res.InternalFields.Add(otpKey); | ||||
|  | ||||
| 				if (parameters[KeyParameter] == null) | ||||
| 				{ | ||||
| 					return res; | ||||
| 				} | ||||
| 				res.TotpSeed = parameters[KeyParameter]; | ||||
|  | ||||
|                 try | ||||
|                 { | ||||
|                     res.TotpSeed = parameters[KeyParameter]; | ||||
|  | ||||
|  | ||||
|                     res.Duration = GetIntOrDefault(parameters, StepParameter, 30).ToString(); | ||||
|                     res.Length = GetIntOrDefault(parameters, SizeParameter, 6).ToString(); | ||||
|  | ||||
|                     res.IsTotpEntry = true; | ||||
|                 } | ||||
|                 catch (Exception e) | ||||
|                 { | ||||
|                     Kp2aLog.Log("Cannot parse seed"); | ||||
|                 } | ||||
| 				 | ||||
| 				res.Duration = GetIntOrDefault(parameters, StepParameter, 30).ToString(); | ||||
| 				res.Length = GetIntOrDefault(parameters, SizeParameter, 6).ToString(); | ||||
|  | ||||
| 				res.IsTotpEntry = true; | ||||
| 				return res; | ||||
|  | ||||
| 			} | ||||
|   | ||||
| @@ -16,6 +16,7 @@ namespace keepass2android | ||||
|             { | ||||
|                 return res; | ||||
|             } | ||||
|             res.InternalFields.Add("otp"); | ||||
|  | ||||
|             string otpUriStart = "otpauth://totp/"; | ||||
|  | ||||
|   | ||||
| @@ -14,14 +14,18 @@ namespace PluginTOTP | ||||
|         public TotpData GetTotpData(IDictionary<string, string> entryFields, Context ctx, bool muteWarnings) | ||||
|         { | ||||
|             TotpData res = new TotpData(); | ||||
|             byte[] pbSecret = (GetOtpSecret(entryFields, "TimeOtp-") ?? MemUtil.EmptyByteArray); | ||||
|             byte[] pbSecret = (GetOtpSecret(entryFields, "TimeOtp-", out string secretFieldKey) ?? MemUtil.EmptyByteArray); | ||||
|  | ||||
|             if (pbSecret.Length == 0) | ||||
|                 return res; | ||||
|  | ||||
|             res.InternalFields.Add(secretFieldKey); | ||||
|  | ||||
|             string strPeriod; | ||||
|             uint uPeriod = 0; | ||||
|             if (entryFields.TryGetValue("TimeOtp-Period", out strPeriod)) | ||||
|             { | ||||
|                 res.InternalFields.Add("TimeOtp-Period"); | ||||
|                 uint.TryParse(strPeriod, out uPeriod); | ||||
|             } | ||||
|  | ||||
| @@ -34,6 +38,7 @@ namespace PluginTOTP | ||||
|             uint uLength = 0; | ||||
|             if (entryFields.TryGetValue("TimeOtp-Length", out strLength)) | ||||
|             { | ||||
|                 res.InternalFields.Add("TimeOtp-Length"); | ||||
|                 uint.TryParse(strLength, out uLength); | ||||
|             } | ||||
|              | ||||
| @@ -42,6 +47,8 @@ namespace PluginTOTP | ||||
|  | ||||
|             string strAlg; | ||||
|             entryFields.TryGetValue("TimeOtp-Algorithm", out strAlg); | ||||
|             if (!string.IsNullOrEmpty(strAlg)) | ||||
|                 res.InternalFields.Add("TimeOtp-Algorithm"); | ||||
|  | ||||
|             res.HashAlgorithm = strAlg; | ||||
|             res.TotpSecret = pbSecret; | ||||
| @@ -52,32 +59,37 @@ namespace PluginTOTP | ||||
|         } | ||||
|  | ||||
|  | ||||
|         private static byte[] GetOtpSecret(IDictionary<string, string> entryFields, string strPrefix) | ||||
|         private static byte[] GetOtpSecret(IDictionary<string, string> entryFields, string strPrefix, out string secretFieldKey) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 string str; | ||||
|                 entryFields.TryGetValue(strPrefix + "Secret", out str); | ||||
|                 if (!string.IsNullOrEmpty(str)) | ||||
|                 secretFieldKey = strPrefix + "Secret"; | ||||
|                 entryFields.TryGetValue(secretFieldKey, out str); | ||||
|                 if (!string.IsNullOrEmpty(str))     | ||||
|                     return StrUtil.Utf8.GetBytes(str); | ||||
|  | ||||
|                 entryFields.TryGetValue(strPrefix + "Secret-Hex", out str); | ||||
|                  | ||||
|                 secretFieldKey = strPrefix + "Secret-Hex"; | ||||
|                 entryFields.TryGetValue(secretFieldKey, out str); | ||||
|                 if (!string.IsNullOrEmpty(str)) | ||||
|                     return MemUtil.HexStringToByteArray(str); | ||||
|  | ||||
|                 entryFields.TryGetValue(strPrefix + "Secret-Base32", out str); | ||||
|                  | ||||
|                 secretFieldKey = strPrefix + "Secret-Base32"; | ||||
|                 entryFields.TryGetValue(secretFieldKey, out str); | ||||
|                 if (!string.IsNullOrEmpty(str)) | ||||
|                     return Base32.Decode(str);  | ||||
|                     | ||||
|                 entryFields.TryGetValue(strPrefix + "Secret-Base64", out str); | ||||
|  | ||||
|                 secretFieldKey = strPrefix + "Secret-Base64"; | ||||
|                 entryFields.TryGetValue(secretFieldKey, out str); | ||||
|                 if (!string.IsNullOrEmpty(str)) | ||||
|                     return Convert.FromBase64String(str); | ||||
|                  | ||||
|             } | ||||
|             catch (Exception e) | ||||
|             { | ||||
|                 Kp2aLog.LogUnexpectedError(e); | ||||
|             } | ||||
|  | ||||
|             secretFieldKey = null; | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -10,8 +10,9 @@ namespace keepass2android | ||||
| { | ||||
| 	class Kp2aTotp | ||||
| 	{ | ||||
|         public const string TotpKey = "TOTP"; | ||||
|  | ||||
| 		readonly ITotpPluginAdapter[] _pluginAdapters = new ITotpPluginAdapter[] | ||||
|         readonly ITotpPluginAdapter[] _pluginAdapters = new ITotpPluginAdapter[] | ||||
|         { | ||||
|             new TrayTotpPluginAdapter(),  | ||||
|             new KeeOtpPluginAdapter(),  | ||||
| @@ -46,7 +47,7 @@ namespace keepass2android | ||||
|                 foreach (ITotpPluginAdapter adapter in _pluginAdapters) | ||||
|                 { | ||||
|                     TotpData totpData = adapter.GetTotpData( | ||||
|                         App.Kp2a.LastOpenedEntry.OutputStrings.ToDictionary(pair => StrUtil.SafeXmlString(pair.Key), | ||||
|                         entry.OutputStrings.ToDictionary(pair => StrUtil.SafeXmlString(pair.Key), | ||||
|                             pair => pair.Value.ReadString()), LocaleManager.LocalizedAppContext, false); | ||||
|                     if (totpData.IsTotpEntry) | ||||
|                     { | ||||
|   | ||||
| @@ -31,12 +31,14 @@ namespace PluginTOTP | ||||
| 		public string TimeCorrectionUrl { get; set; } | ||||
|  | ||||
|         public string HashAlgorithm { get; set; } | ||||
|  | ||||
|          | ||||
|         public bool IsDefaultRfc6238 | ||||
|         { | ||||
|             get { return Length == "6" && Duration == "30" && (HashAlgorithm == null || HashAlgorithm == HashSha1); } | ||||
|         } | ||||
|  | ||||
|         public List<string> InternalFields { get; set; } = new List<string>(); | ||||
|  | ||||
|         public static TotpData MakeDefaultRfc6238() | ||||
|         { | ||||
|             return new TotpData() | ||||
|   | ||||
| @@ -106,6 +106,8 @@ namespace PluginTOTP | ||||
| 					{ | ||||
| 						bool NoTimeCorrection = false; | ||||
| 						string[] Settings = SettingsGet(entryFields); | ||||
| 						res.InternalFields.Add(SettingsFieldName); | ||||
| 						res.InternalFields.Add(SeedFieldName); | ||||
| 						res.Duration = Settings[0]; | ||||
| 						res.Length = Settings[1]; | ||||
|                         if (res.Length == "S") | ||||
|   | ||||
| @@ -13,7 +13,7 @@ namespace PluginTOTP | ||||
| { | ||||
| 	class UpdateTotpTimerTask: TimerTask | ||||
| 	{ | ||||
| 		public const string TotpKey = "TOTP"; | ||||
| 		public const string TotpKey = Kp2aTotp.TotpKey; | ||||
| 		private readonly Context _context; | ||||
| 		private readonly ITotpPluginAdapter _adapter; | ||||
|  | ||||
|   | ||||
| @@ -6,9 +6,13 @@ using Android.OS; | ||||
| using Android.Widget; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Threading; | ||||
| using KeePassLib; | ||||
| using KeePassLib.Security; | ||||
| using KeePassLib.Utility; | ||||
| using KeeTrayTOTP.Libraries; | ||||
| using Android.Content.Res; | ||||
| using Android.Preferences; | ||||
|  | ||||
| namespace keepass2android | ||||
| { | ||||
| @@ -339,9 +343,17 @@ namespace keepass2android | ||||
|  | ||||
| 		} | ||||
|  | ||||
| 		public virtual void CompleteOnCreateEntryActivity(EntryActivity activity) | ||||
| 		public virtual void CompleteOnCreateEntryActivity(EntryActivity activity, Thread notifyPluginsOnOpenThread) | ||||
| 		{ | ||||
| 			activity.StartNotificationsService(false); | ||||
| 			//this default implementation is executed when we're opening an entry manually, i.e. without search/autofill. | ||||
| 			//We only activate the keyboard if this is enabled in "silent mode" | ||||
|             ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(activity); | ||||
|             bool activateKeyboard = prefs.GetBoolean("kp2a_switch_rooted", false) && | ||||
|                                     !prefs.GetBoolean( | ||||
|                                         activity.GetString(Resource.String | ||||
|                                             .OpenKp2aKeyboardAutomaticallyOnlyAfterSearch_key), false); | ||||
|  | ||||
|             activity.StartNotificationsService(activateKeyboard); | ||||
| 		} | ||||
|  | ||||
| 		public virtual void PopulatePasswordAccessServiceIntent(Intent intent) | ||||
| @@ -353,7 +365,8 @@ namespace keepass2android | ||||
|         protected static bool GetBoolFromBundle(Bundle b, string key, bool defaultValue) | ||||
|         { | ||||
|             bool boolValue; | ||||
|             if (!Boolean.TryParse(b.GetString(key), out boolValue)) | ||||
|             string stringValue = b.GetString(key); | ||||
|             if (!Boolean.TryParse(stringValue, out boolValue)) | ||||
|             { | ||||
|                 boolValue = defaultValue; | ||||
|             } | ||||
| @@ -363,7 +376,8 @@ namespace keepass2android | ||||
|         protected static int GetIntFromBundle(Bundle b, string key, int defaultValue) | ||||
|         { | ||||
|             int intValue; | ||||
|             if (!Int32.TryParse(b.GetString(key), out intValue)) | ||||
|             var strValue = b.GetString(key); | ||||
|             if (!Int32.TryParse(strValue, out intValue)) | ||||
|             { | ||||
|                 intValue = defaultValue; | ||||
|             } | ||||
| @@ -383,7 +397,7 @@ namespace keepass2android | ||||
| 	/// User is about to search an entry for a given URL | ||||
| 	/// </summary> | ||||
| 	/// Derive from SelectEntryTask. This means that as soon as an Entry is opened, we're returning with | ||||
| 	/// ExitAfterTaskComplete. This also allows te specify the flag if we need to display the user notifications. | ||||
| 	/// ExitAfterTaskComplete. This also allows to specify the flag if we need to display the user notifications. | ||||
| 	public class SearchUrlTask: SelectEntryTask | ||||
| 	{ | ||||
| 	    public SearchUrlTask() | ||||
| @@ -392,8 +406,9 @@ namespace keepass2android | ||||
| 	    } | ||||
|  | ||||
|         public const String UrlToSearchKey = "UrlToSearch"; | ||||
|         public const String AutoReturnFromQueryKey = "AutoReturnFromQuery"; | ||||
|  | ||||
| 		public string UrlToSearchFor | ||||
|         public string UrlToSearchFor | ||||
| 		{ | ||||
| 			get; | ||||
| 			set; | ||||
| @@ -416,7 +431,7 @@ namespace keepass2android | ||||
|             } | ||||
| 		} | ||||
|  | ||||
| 	    public const String AutoReturnFromQueryKey = "AutoReturnFromQuery"; | ||||
| 	     | ||||
|  | ||||
|         public bool AutoReturnFromQuery { get; set; } | ||||
|  | ||||
| @@ -424,15 +439,19 @@ namespace keepass2android | ||||
| 		{ | ||||
| 			if (String.IsNullOrEmpty(UrlToSearchFor)) | ||||
| 			{ | ||||
| 				GroupActivity.Launch(act, new SelectEntryTask() { ShowUserNotifications =  ShowUserNotifications}, new ActivityLaunchModeRequestCode(0)); | ||||
| 				GroupActivity.Launch(act, new SelectEntryTask() {  | ||||
|                         ShowUserNotifications =  ShowUserNotifications, | ||||
|                         CopyTotpToClipboard = CopyTotpToClipboard, | ||||
|                         ActivateKeyboard = ActivateKeyboard | ||||
|                 },  | ||||
|                     new ActivityLaunchModeRequestCode(0)); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				ShareUrlResults.Launch(act, this, new ActivityLaunchModeRequestCode(0)); | ||||
| 			} | ||||
| 			 | ||||
|  | ||||
| 			//removed. this causes an issue in the following workflow: | ||||
|             //removed. this causes an issue in the following workflow: | ||||
| 			//When the user wants to find an entry for a URL but has the wrong database open he needs  | ||||
| 			//to switch to another database. But the Task is removed already the first time when going through PasswordActivity  | ||||
| 			// (with the wrong db). | ||||
| @@ -453,7 +472,7 @@ namespace keepass2android | ||||
| 			intent.PutExtra(UrlToSearchKey, UrlToSearchFor); | ||||
| 		} | ||||
|  | ||||
| 	    public override void CompleteOnCreateEntryActivity(EntryActivity activity) | ||||
| 	    public override void CompleteOnCreateEntryActivity(EntryActivity activity, Thread notifyPluginsOnOpenThread) | ||||
| 	    { | ||||
|             if (App.Kp2a.LastOpenedEntry != null) | ||||
| 	            App.Kp2a.LastOpenedEntry.SearchUrl = UrlToSearchFor; | ||||
| @@ -462,18 +481,18 @@ namespace keepass2android | ||||
|             //if the database is readonly (or no URL exists), don't offer to modify the URL | ||||
|             if ((App.Kp2a.CurrentDb.CanWrite == false) || (String.IsNullOrEmpty(UrlToSearchFor) || keepass2android.ShareUrlResults.GetSearchResultsForUrl(UrlToSearchFor).Entries.Any(e => e == activity.Entry) )) | ||||
|             { | ||||
|                 base.CompleteOnCreateEntryActivity(activity); | ||||
|                 base.CompleteOnCreateEntryActivity(activity, notifyPluginsOnOpenThread); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             AskAddUrlThenCompleteCreate(activity, UrlToSearchFor); | ||||
|             AskAddUrlThenCompleteCreate(activity, UrlToSearchFor, notifyPluginsOnOpenThread); | ||||
|         } | ||||
|  | ||||
|  | ||||
|         /// <summary> | ||||
|         /// brings up a dialog asking the user whether he wants to add the given URL to the entry for automatic finding | ||||
|         /// </summary> | ||||
|         public void AskAddUrlThenCompleteCreate(EntryActivity activity, string url) | ||||
|         public void AskAddUrlThenCompleteCreate(EntryActivity activity, string url, Thread notifyPluginsOnOpenThread) | ||||
|         { | ||||
|             AlertDialog.Builder builder = new AlertDialog.Builder(activity); | ||||
|             builder.SetTitle(activity.GetString(Resource.String.AddUrlToEntryDialog_title)); | ||||
| @@ -482,12 +501,13 @@ namespace keepass2android | ||||
|  | ||||
|             builder.SetPositiveButton(activity.GetString(Resource.String.yes), (dlgSender, dlgEvt) => | ||||
|             { | ||||
|                 activity.AddUrlToEntry(url, (EntryActivity thenActiveActivity) => base.CompleteOnCreateEntryActivity(thenActiveActivity)); | ||||
|                 activity.AddUrlToEntry(url, (EntryActivity thenActiveActivity) => base.CompleteOnCreateEntryActivity(thenActiveActivity, notifyPluginsOnOpenThread | ||||
| )); | ||||
|             }); | ||||
|  | ||||
|             builder.SetNegativeButton(activity.GetString(Resource.String.no), (dlgSender, dlgEvt) => | ||||
|             { | ||||
|                 base.CompleteOnCreateEntryActivity(activity); | ||||
|                 base.CompleteOnCreateEntryActivity(activity, notifyPluginsOnOpenThread); | ||||
|             }); | ||||
|  | ||||
|             Dialog dialog = builder.Create(); | ||||
| @@ -495,8 +515,46 @@ namespace keepass2android | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public class OpenSpecificEntryTask : SelectEntryTask | ||||
|     { | ||||
|         public OpenSpecificEntryTask() | ||||
|         { | ||||
|         } | ||||
|  | ||||
|     public enum ShowUserNotificationsMode | ||||
|         public const String EntryUuidKey = "EntryUuid"; | ||||
|  | ||||
|         public string EntryUuid | ||||
|         { | ||||
|             get; | ||||
|             set; | ||||
|         } | ||||
|  | ||||
|         public override void Setup(Bundle b) | ||||
|         { | ||||
|             base.Setup(b); | ||||
|             EntryUuid = b.GetString(EntryUuidKey); | ||||
|              | ||||
|         } | ||||
|         public override IEnumerable<IExtra> Extras | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 foreach (IExtra e in base.Extras) | ||||
|                     yield return e; | ||||
|                  | ||||
|                 yield return new StringExtra { Key = EntryUuidKey, Value = EntryUuid }; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public override void LaunchFirstGroupActivity(Activity act) | ||||
|         { | ||||
|             ShareUrlResults.Launch(act, this, new ActivityLaunchModeRequestCode(0)); | ||||
|         } | ||||
| 		 | ||||
|     } | ||||
|  | ||||
|  | ||||
|     public enum ActivationCondition | ||||
|     { | ||||
|         Never, | ||||
|         WhenTotp, | ||||
| @@ -509,29 +567,34 @@ namespace keepass2android | ||||
| 	{ | ||||
| 		public SelectEntryTask() | ||||
| 		{ | ||||
| 			ShowUserNotifications = ShowUserNotificationsMode.Always; | ||||
| 			ShowUserNotifications = ActivationCondition.Always; | ||||
| 			CloseAfterCreate = true; | ||||
|             ActivateKeyboard = true; | ||||
|             ActivateKeyboard = ActivationCondition.Never; | ||||
|             CopyTotpToClipboard = false; | ||||
|         } | ||||
|  | ||||
| 		public const String ShowUserNotificationsKey = "ShowUserNotifications"; | ||||
|  | ||||
|  | ||||
|  | ||||
|         public ShowUserNotificationsMode ShowUserNotifications { get; set; } | ||||
|         public ActivationCondition ShowUserNotifications { get; set; } | ||||
|  | ||||
| 		public const String CloseAfterCreateKey = "CloseAfterCreate"; | ||||
|         public const String ActivateKeyboardKey = "ActivateKeyboard"; | ||||
|         public const String CopyTotpToClipboardKey = "CopyTotpToClipboard"; | ||||
|  | ||||
|         public bool CloseAfterCreate { get; set; } | ||||
|         public bool ActivateKeyboard { get; set; } | ||||
|         public ActivationCondition ActivateKeyboard { get; set; } | ||||
|  | ||||
|         public bool CopyTotpToClipboard { get; set; } | ||||
|  | ||||
|  | ||||
|         public override void Setup(Bundle b) | ||||
| 		{ | ||||
| 			ShowUserNotifications = (ShowUserNotificationsMode) GetIntFromBundle(b, ShowUserNotificationsKey, (int)ShowUserNotificationsMode.Always); | ||||
| 			ShowUserNotifications = (ActivationCondition) GetIntFromBundle(b, ShowUserNotificationsKey, (int)ActivationCondition.Always); | ||||
| 			CloseAfterCreate = GetBoolFromBundle(b, CloseAfterCreateKey, true); | ||||
|             ActivateKeyboard = GetBoolFromBundle(b, ActivateKeyboardKey, true); | ||||
|             ActivateKeyboard = (ActivationCondition)GetIntFromBundle(b, ActivateKeyboardKey, (int)ActivationCondition.Always); | ||||
|             CopyTotpToClipboard = GetBoolFromBundle(b, CopyTotpToClipboardKey, false); | ||||
|         } | ||||
|  | ||||
|  | ||||
| @@ -541,31 +604,59 @@ namespace keepass2android | ||||
| 			{ | ||||
| 				yield return new StringExtra { Key = ShowUserNotificationsKey, Value = ((int)ShowUserNotifications).ToString() }; | ||||
| 				yield return new StringExtra { Key = CloseAfterCreateKey, Value = CloseAfterCreate.ToString() }; | ||||
|                 yield return new StringExtra { Key = ActivateKeyboardKey, Value = ActivateKeyboard.ToString() }; | ||||
|                 yield return new StringExtra { Key = ActivateKeyboardKey, Value = ((int)ActivateKeyboard).ToString() }; | ||||
|                 yield return new StringExtra { Key = CopyTotpToClipboardKey, Value = CopyTotpToClipboard.ToString() }; | ||||
|             } | ||||
| 		} | ||||
|  | ||||
| 		public override void CompleteOnCreateEntryActivity(EntryActivity activity) | ||||
| 		public override void CompleteOnCreateEntryActivity(EntryActivity activity, Thread notifyPluginsOnOpenThread) | ||||
| 		{ | ||||
| 		    Context ctx = activity; | ||||
| 		    if (ctx == null) | ||||
| 		        ctx = LocaleManager.LocalizedAppContext; | ||||
|  | ||||
| 			if ((ShowUserNotifications == ShowUserNotificationsMode.Always) | ||||
|                 || ((ShowUserNotifications == ShowUserNotificationsMode.WhenTotp) && new Kp2aTotp().TryGetAdapter(new PwEntryOutput(activity.Entry, App.Kp2a.CurrentDb)) != null)) | ||||
| 			{ | ||||
| 				//show the notifications | ||||
| 				activity.StartNotificationsService(ActivateKeyboard); | ||||
| 			} | ||||
|             var pwEntryOutput = new PwEntryOutput(activity.Entry, App.Kp2a.CurrentDb); | ||||
|             var totpPluginAdapter = new Kp2aTotp().TryGetAdapter(pwEntryOutput); | ||||
|             bool isTotpEntry = totpPluginAdapter != null; | ||||
| 			 | ||||
|             bool activateKeyboard = ActivateKeyboard == ActivationCondition.Always || (ActivateKeyboard == ActivationCondition.WhenTotp && isTotpEntry); | ||||
|  | ||||
|             if ((ShowUserNotifications == ActivationCondition.Always) | ||||
|                 || ((ShowUserNotifications == ActivationCondition.WhenTotp) && isTotpEntry) | ||||
|                 || activateKeyboard) | ||||
|             { | ||||
|                 //show the notifications | ||||
|                 activity.StartNotificationsService(activateKeyboard); | ||||
|             } | ||||
| 			else | ||||
| 			{ | ||||
| 				//to avoid getting into inconsistent state (LastOpenedEntry and Notifications): clear notifications: | ||||
| 				CopyToClipboardService.CancelNotifications(activity); | ||||
| 			} | ||||
| 			if (CloseAfterCreate) | ||||
| 			{ | ||||
| 				//close | ||||
| 				activity.CloseAfterTaskComplete();	 | ||||
|  | ||||
|             if (CopyTotpToClipboard && isTotpEntry) | ||||
|             { | ||||
|                 Dictionary<string, string> entryFields = pwEntryOutput.OutputStrings.ToDictionary(pair => StrUtil.SafeXmlString(pair.Key), pair => pair.Value.ReadString()); | ||||
|                 var totpData= totpPluginAdapter.GetTotpData(entryFields, activity, true); | ||||
|                 if (totpData.IsTotpEntry) | ||||
|                 { | ||||
|                     TOTPProvider prov = new TOTPProvider(totpData); | ||||
|                     string totp = prov.GenerateByByte(totpData.TotpSecret); | ||||
|                     CopyToClipboardService.CopyValueToClipboardWithTimeout(activity, totp, true); | ||||
|  | ||||
|                     Toast.MakeText(activity, activity.GetString(Resource.String.TotpCopiedToClipboard), | ||||
|                         ToastLength.Long).Show(); | ||||
|                 } | ||||
|  | ||||
|                  | ||||
|             } | ||||
|  | ||||
|             if (CloseAfterCreate) | ||||
|             { | ||||
| 				//give plugins and TOTP time to do their work: | ||||
|                 notifyPluginsOnOpenThread.Join(TimeSpan.FromSeconds(1)); | ||||
|                 //close | ||||
|                 activity.CloseAfterTaskComplete();	 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| @@ -629,8 +720,8 @@ namespace keepass2android | ||||
| 	public class CreateEntryThenCloseTask: AppTask | ||||
| 	{ | ||||
| 		public CreateEntryThenCloseTask() | ||||
| 		{ | ||||
| 			ShowUserNotifications = ShowUserNotificationsMode.Always; | ||||
|         { | ||||
| 			ShowUserNotifications = ActivationCondition.Always; | ||||
| 		} | ||||
|  | ||||
| 	    public override bool CanActivateSearchViewOnStart | ||||
| @@ -670,13 +761,13 @@ namespace keepass2android | ||||
|  | ||||
| 		public IList<string> ProtectedFieldsList { get; set; } | ||||
|  | ||||
| 		public ShowUserNotificationsMode ShowUserNotifications { get; set; } | ||||
| 		public ActivationCondition ShowUserNotifications { get; set; } | ||||
|  | ||||
|  | ||||
| 		public override void Setup(Bundle b) | ||||
| 		{ | ||||
| 			 | ||||
| 			ShowUserNotifications = (ShowUserNotificationsMode)GetIntFromBundle(b,ShowUserNotificationsKey, (int)ShowUserNotificationsMode.Always); | ||||
| 			ShowUserNotifications = (ActivationCondition)GetIntFromBundle(b,ShowUserNotificationsKey, (int)ActivationCondition.Always); | ||||
| 			 | ||||
| 			Url = b.GetString(UrlKey); | ||||
| 			AllFields = b.GetString(AllFieldsKey); | ||||
| @@ -724,15 +815,15 @@ namespace keepass2android | ||||
| 		public override void AfterAddNewEntry(EntryEditActivity entryEditActivity, PwEntry newEntry) | ||||
| 		{ | ||||
| 			EntryActivity.Launch(entryEditActivity, newEntry, -1,  | ||||
| 				new SelectEntryTask { ShowUserNotifications = this.ShowUserNotifications},  | ||||
| 				new SelectEntryTask() { ShowUserNotifications = this.ShowUserNotifications, ActivateKeyboard = ActivationCondition.Never },  | ||||
| 				ActivityFlags.ForwardResult); | ||||
| 			//no need to call Finish here, that's done in EntryEditActivity ("closeOrShowError")	 | ||||
| 		} | ||||
| 		 | ||||
| 		public override void CompleteOnCreateEntryActivity(EntryActivity activity) | ||||
| 		public override void CompleteOnCreateEntryActivity(EntryActivity activity, Thread notifyPluginsOnOpenThread) | ||||
| 		{ | ||||
| 			//if the user selects an entry before creating the new one, we're not closing the app | ||||
| 			base.CompleteOnCreateEntryActivity(activity); | ||||
| 			base.CompleteOnCreateEntryActivity(activity, notifyPluginsOnOpenThread); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -1985,6 +1985,9 @@ | ||||
|   <ItemGroup> | ||||
|     <AndroidResource Include="Resources\drawable-mdpi\ic_storage_pcloudall.png" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <AndroidResource Include="Resources\drawable-xhdpi\ic_entry_totp.png" /> | ||||
|   </ItemGroup> | ||||
|   <Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" /> | ||||
|   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. | ||||
|     Other similar extension points exist, see Microsoft.Common.targets. | ||||
|   | ||||
| @@ -20,6 +20,7 @@ using AndroidX.AutoFill.Inline; | ||||
| using AndroidX.AutoFill.Inline.V1; | ||||
| using Java.Util.Concurrent.Atomic; | ||||
| using keepass2android.services.AutofillBase.model; | ||||
| using KeePassLib; | ||||
| using Kp2aAutofillParser; | ||||
|  | ||||
| namespace keepass2android.services.AutofillBase | ||||
| @@ -29,7 +30,7 @@ namespace keepass2android.services.AutofillBase | ||||
|         PendingIntent GetAuthPendingIntentForResponse(Context context, string query, string queryDomain, string queryPackage, | ||||
|             bool isManualRequest, bool autoReturnFromQuery, AutofillServiceBase.DisplayWarning warning); | ||||
|  | ||||
|         PendingIntent GetAuthPendingIntentForWarning(Context context, string query, string queryDomain, string queryPackage, AutofillServiceBase.DisplayWarning warning); | ||||
|         PendingIntent GetAuthPendingIntentForWarning(Context context, PwUuid entryUuid, AutofillServiceBase.DisplayWarning warning); | ||||
|  | ||||
|         PendingIntent GetDisablePendingIntentForResponse(Context context, string query,  | ||||
|             bool isManualRequest, bool isDisable); | ||||
| @@ -262,33 +263,42 @@ namespace keepass2android.services.AutofillBase | ||||
|         { | ||||
|             List<Dataset> result = new List<Dataset>(); | ||||
|             Kp2aLog.Log("AF: BuildEntryDatasets"); | ||||
|             var suggestedEntries = GetSuggestedEntries(query).ToDictionary(e => e.DatasetName, e => e); | ||||
|             Dictionary<PwEntryOutput, FilledAutofillFieldCollection<ViewNodeInputField>> suggestedEntries = GetSuggestedEntries(query); | ||||
|             Kp2aLog.Log("AF: BuildEntryDatasets found " + suggestedEntries.Count + " entries"); | ||||
|              | ||||
|             int count = 0; | ||||
|             foreach (var filledAutofillFieldCollection in suggestedEntries.Values) | ||||
|  | ||||
|             var totpHelper = new Kp2aTotp(); | ||||
|  | ||||
|             foreach (var kvp in suggestedEntries) | ||||
|             { | ||||
|                 var filledAutofillFieldCollection = kvp.Value; | ||||
|                 PwEntryOutput entry = kvp.Key; | ||||
|  | ||||
|                 if (filledAutofillFieldCollection == null) | ||||
|                     continue; | ||||
|  | ||||
|                 var inlinePresentationSpec = AutofillHelper.ExtractSpec(inlinePresentationSpecs, count); | ||||
|  | ||||
|                 if (warning == DisplayWarning.None) | ||||
|                 if ((warning == DisplayWarning.None) | ||||
|                     && (totpHelper.TryGetAdapter(entry) == null)) | ||||
|                 { | ||||
|            | ||||
|                     //no special dataset, we can immediately return the field collection | ||||
|                     FilledAutofillFieldCollection<ViewNodeInputField> partitionData = | ||||
|                         AutofillHintsHelper.FilterForPartition(filledAutofillFieldCollection, parser.AutofillFields.FocusedAutofillCanonicalHints); | ||||
|  | ||||
|                     Kp2aLog.Log("AF: Add dataset"); | ||||
|  | ||||
|                     result.Add(AutofillHelper.NewDataset(this, parser.AutofillFields, partitionData, IntentBuilder,  | ||||
|                     result.Add(AutofillHelper.NewDataset(this, parser.AutofillFields, partitionData, IntentBuilder, | ||||
|                         inlinePresentationSpec)); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     //return an "auth" dataset (actually for just warning the user in case domain/package dont match) | ||||
|  | ||||
|                     //return an "auth" dataset (actually for just warning the user in case domain/package dont match and/or to make sure that we open the EntryActivity, | ||||
|                     // thus opening the entry notification in case of TOTP) | ||||
|                     PendingIntent pendingIntent = | ||||
|                         IntentBuilder.GetAuthPendingIntentForWarning(this, query, queryDomain, queryPackage, warning); | ||||
|                         IntentBuilder.GetAuthPendingIntentForWarning(this, entry.Uuid, warning); | ||||
|                     var datasetName = filledAutofillFieldCollection.DatasetName; | ||||
|                     if (datasetName == null) | ||||
|                     { | ||||
| @@ -320,7 +330,7 @@ namespace keepass2android.services.AutofillBase | ||||
|  | ||||
|         } | ||||
|  | ||||
|         protected abstract List<FilledAutofillFieldCollection<ViewNodeInputField>> GetSuggestedEntries(string query); | ||||
|         protected abstract Dictionary<PwEntryOutput, FilledAutofillFieldCollection<ViewNodeInputField>> GetSuggestedEntries(string query); | ||||
|  | ||||
|         public enum DisplayWarning | ||||
|         { | ||||
|   | ||||
| @@ -24,7 +24,7 @@ namespace keepass2android.services.AutofillBase | ||||
|     { | ||||
|         protected Intent ReplyIntent; | ||||
|  | ||||
|  | ||||
|         public static string ExtraUuidString => "EXTRA_UUID_STRING"; | ||||
|         public static string ExtraQueryString => "EXTRA_QUERY_STRING"; | ||||
|         public static string ExtraQueryPackageString => "EXTRA_QUERY_PACKAGE_STRING"; | ||||
|         public static string ExtraQueryDomainString => "EXTRA_QUERY_DOMAIN_STRING"; | ||||
| @@ -50,9 +50,10 @@ namespace keepass2android.services.AutofillBase | ||||
|             } | ||||
|  | ||||
|             string requestedUrl = Intent.GetStringExtra(ExtraQueryString); | ||||
|             if (requestedUrl == null) | ||||
|             string requestedUuid = Intent.GetStringExtra(ExtraUuidString); | ||||
|             if (requestedUrl == null && requestedUuid == null) | ||||
|             { | ||||
|                 Kp2aLog.Log("ChooseForAutofillActivityBase: no requestedUrl "); | ||||
|                 Kp2aLog.Log("ChooseForAutofillActivityBase: no requestedUrl and no requestedUuid"); | ||||
|                 Toast.MakeText(this, "Cannot execute query for null.", ToastLength.Long).Show(); | ||||
|                 RestartApp(); | ||||
|                 return; | ||||
| @@ -134,18 +135,30 @@ namespace keepass2android.services.AutofillBase | ||||
|         private void Proceed() | ||||
|         { | ||||
|             string requestedUrl = Intent.GetStringExtra(ExtraQueryString); | ||||
|             string requestedUuid = Intent.GetStringExtra(ExtraUuidString); | ||||
|  | ||||
|             var i = GetQueryIntent(requestedUrl, Intent.GetBooleanExtra(ExtraAutoReturnFromQuery, true), Intent.GetBooleanExtra(ExtraUseLastOpenedEntry, false)); | ||||
|             if (i == null) | ||||
|             if (requestedUuid != null) | ||||
|             { | ||||
|                 //GetQueryIntent returns null if no query is required | ||||
|                 ReturnSuccess(); | ||||
|                 var i = GetOpenEntryIntent(requestedUuid); | ||||
|                 StartActivityForResult(i, RequestCodeQuery); | ||||
|             } | ||||
|             else | ||||
|                 StartActivityForResult(i, RequestCodeQuery); | ||||
|             { | ||||
|                 var i = GetQueryIntent(requestedUrl, Intent.GetBooleanExtra(ExtraAutoReturnFromQuery, true), Intent.GetBooleanExtra(ExtraUseLastOpenedEntry, false)); | ||||
|                 if (i == null) | ||||
|                 { | ||||
|                     //GetQueryIntent returns null if no query is required | ||||
|                     ReturnSuccess(); | ||||
|                 } | ||||
|                 else | ||||
|                     StartActivityForResult(i, RequestCodeQuery); | ||||
|             } | ||||
|  | ||||
|              | ||||
|         } | ||||
|  | ||||
|         protected abstract Intent GetQueryIntent(string requestedUrl, bool autoReturnFromQuery, bool useLastOpenedEntry); | ||||
|         protected abstract Intent GetOpenEntryIntent(string entryUuid); | ||||
|  | ||||
|         protected void RestartApp() | ||||
|         { | ||||
|   | ||||
| @@ -501,24 +501,8 @@ namespace keepass2android | ||||
|                 if (hasKeyboardDataNow) | ||||
|                 { | ||||
|                     notBuilder.AddKeyboardAccess(); | ||||
|                     if (prefs.GetBoolean("kp2a_switch_rooted", false)) | ||||
|                     { | ||||
|                         //switch rooted | ||||
|                         bool onlySwitchOnSearch = prefs.GetBoolean(GetString(Resource.String.OpenKp2aKeyboardAutomaticallyOnlyAfterSearch_key), false); | ||||
|                         if (activateKeyboard || (!onlySwitchOnSearch)) | ||||
|                         { | ||||
|                             ActivateKp2aKeyboard(); | ||||
|                         } | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         //if the app is about to be closed again (e.g. after searching for a URL and returning to the browser: | ||||
|                         // automatically bring up the Keyboard selection dialog | ||||
|                         if ((activateKeyboard) && prefs.GetBoolean(GetString(Resource.String.OpenKp2aKeyboardAutomatically_key), Resources.GetBoolean(Resource.Boolean.OpenKp2aKeyboardAutomatically_default))) | ||||
|                         { | ||||
|                             ActivateKp2aKeyboard(); | ||||
|                         } | ||||
|                     } | ||||
|                     if (activateKeyboard) | ||||
|                         ActivateKp2aKeyboard(); | ||||
|  | ||||
|                 } | ||||
|  | ||||
| @@ -548,31 +532,6 @@ namespace keepass2android | ||||
|              | ||||
|         } | ||||
|  | ||||
|         public void ActivateKeyboardIfAppropriate(bool closeAfterCreate, ISharedPreferences prefs) | ||||
|         { | ||||
|             if (prefs.GetBoolean("kp2a_switch_rooted", false)) | ||||
|             { | ||||
|                 //switch rooted | ||||
|                 bool onlySwitchOnSearch = prefs.GetBoolean( | ||||
|                     GetString(Resource.String.OpenKp2aKeyboardAutomaticallyOnlyAfterSearch_key), false); | ||||
|                 if (closeAfterCreate || (!onlySwitchOnSearch)) | ||||
|                 { | ||||
|                     ActivateKp2aKeyboard(); | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 //if the app is about to be closed again (e.g. after searching for a URL and returning to the browser: | ||||
|                 // automatically bring up the Keyboard selection dialog | ||||
|                 if ((closeAfterCreate) && | ||||
|                     prefs.GetBoolean(GetString(Resource.String.OpenKp2aKeyboardAutomatically_key), | ||||
|                         Resources.GetBoolean(Resource.Boolean.OpenKp2aKeyboardAutomatically_default))) | ||||
|                 { | ||||
|                     ActivateKp2aKeyboard(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private bool ClearNotifications() | ||||
|         { | ||||
|             // Notification Manager | ||||
| @@ -587,6 +546,7 @@ namespace keepass2android | ||||
|             return hadKeyboardData; | ||||
|         } | ||||
|  | ||||
|  | ||||
|         bool MakeAccessibleForKeyboard(PwEntryOutput entry, string searchUrl) | ||||
|         { | ||||
| #if EXCLUDE_KEYBOARD | ||||
| @@ -595,38 +555,41 @@ namespace keepass2android | ||||
|             bool hasData = false; | ||||
|             Keepass2android.Kbbridge.KeyboardDataBuilder kbdataBuilder = new Keepass2android.Kbbridge.KeyboardDataBuilder(); | ||||
|  | ||||
|             String[] keys = {PwDefs.UserNameField, | ||||
|             String[] standardKeys = {PwDefs.UserNameField, | ||||
|                 PwDefs.PasswordField, | ||||
|                 Kp2aTotp.TotpKey, | ||||
|                 PwDefs.UrlField, | ||||
|                 PwDefs.NotesField, | ||||
|                 PwDefs.TitleField | ||||
|             }; | ||||
|             int[] resIds = {Resource.String.entry_user_name, | ||||
|                 Resource.String.entry_password, | ||||
|                 0, | ||||
|                 Resource.String.entry_url, | ||||
|                 Resource.String.entry_comment, | ||||
|                 Resource.String.entry_title }; | ||||
|  | ||||
|             //add standard fields: | ||||
|             int i = 0; | ||||
|             foreach (string key in keys) | ||||
|             foreach (string key in standardKeys) | ||||
|             { | ||||
|                 String value = entry.OutputStrings.ReadSafe(key); | ||||
|  | ||||
|                 if (value.Length > 0) | ||||
|                 { | ||||
|                     kbdataBuilder.AddString(key, GetString(resIds[i]), value); | ||||
|                     kbdataBuilder.AddString(key, resIds[i] > 0 ? GetString(resIds[i]) : key, value); | ||||
|                     hasData = true; | ||||
|                 } | ||||
|                 i++; | ||||
|             } | ||||
|             //add additional fields: | ||||
|             var totpData = new Kp2aTotp().TryGetTotpData(entry); | ||||
|             foreach (var pair in entry.OutputStrings) | ||||
|             { | ||||
|                 var key = pair.Key; | ||||
|                 var value = pair.Value.ReadString(); | ||||
|  | ||||
|                 if (!PwDefs.IsStandardField(key)) | ||||
|                 if (!standardKeys.Contains(key) && totpData?.InternalFields.Contains(key) != true) | ||||
|                 { | ||||
|                     kbdataBuilder.AddString(pair.Key, pair.Key, value); | ||||
|                     hasData = true; | ||||
| @@ -865,14 +828,9 @@ namespace keepass2android | ||||
|                 { | ||||
|                     //let's bring up the keyboard switching dialog. | ||||
|                     //Unfortunately this no longer works starting with Android 9 if our app is not in foreground. | ||||
|                     bool mustUseHelperActivity = false; | ||||
|                     if ((int)Build.VERSION.SdkInt >= 28) | ||||
|                     { | ||||
|                         ActivityManager.RunningAppProcessInfo appProcessInfo = new ActivityManager.RunningAppProcessInfo(); | ||||
|                         ActivityManager.GetMyMemoryState(appProcessInfo); | ||||
|                         //at least on Samsung devices, we always need the helper activity | ||||
|                         mustUseHelperActivity = (appProcessInfo.Importance != Importance.Foreground) || (Build.Manufacturer != "Google"); | ||||
|                     } | ||||
|                     //first it seemed to be required for Samsung mostly, but there are use cases where it is required for other devices as well. | ||||
|                     //Let's be sure and use the helper activity. | ||||
|                     bool mustUseHelperActivity = (int)Build.VERSION.SdkInt >= 28; | ||||
|                     if (mustUseHelperActivity) | ||||
|                     { | ||||
|                         try | ||||
|   | ||||
| @@ -10,6 +10,7 @@ using Android.OS; | ||||
| using Android.Runtime; | ||||
| using Android.Views; | ||||
| using Android.Widget; | ||||
| using AndroidX.Preference; | ||||
| using KeePass.Util.Spr; | ||||
| using keepass2android.services.AutofillBase; | ||||
| using keepass2android.services.AutofillBase.model; | ||||
| @@ -27,6 +28,32 @@ namespace keepass2android.services.Kp2aAutofill | ||||
|         Permission = "keepass2android." + AppNames.PackagePart + ".permission.Kp2aChooseAutofill")] | ||||
|     public class ChooseForAutofillActivity : ChooseForAutofillActivityBase | ||||
|     { | ||||
|         public bool ActivateKeyboardWhenTotpPreference | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 return PreferenceManager.GetDefaultSharedPreferences(this) | ||||
|                     .GetBoolean("AutoFillTotp_prefs_ActivateKeyboard_key", false); | ||||
|             } | ||||
|         } | ||||
|         public bool CopyTotpToClipboardPreference | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 return PreferenceManager.GetDefaultSharedPreferences(this) | ||||
|                     .GetBoolean("AutoFillTotp_prefs_CopyTotpToClipboard_key", true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public bool ShowNotificationPreference | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 return PreferenceManager.GetDefaultSharedPreferences(this) | ||||
|                     .GetBoolean("AutoFillTotp_prefs_ShowNotification_key", true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         protected override Intent GetQueryIntent(string requestedUrl, bool autoReturnFromQuery, bool useLastOpenedEntry) | ||||
|         { | ||||
|             if (useLastOpenedEntry && (App.Kp2a.LastOpenedEntry?.SearchUrl == requestedUrl)) | ||||
| @@ -37,7 +64,33 @@ namespace keepass2android.services.Kp2aAutofill | ||||
|             //will return the results later | ||||
|             Intent i = new Intent(this, typeof(SelectCurrentDbActivity)); | ||||
|             //don't show user notifications when an entry is opened. | ||||
|             var task = new SearchUrlTask() { UrlToSearchFor = requestedUrl, ShowUserNotifications = ShowUserNotificationsMode.WhenTotp, AutoReturnFromQuery = autoReturnFromQuery, ActivateKeyboard = false }; | ||||
|             var task = new SearchUrlTask() | ||||
|             { | ||||
|                 UrlToSearchFor = requestedUrl, | ||||
|                 AutoReturnFromQuery = autoReturnFromQuery | ||||
|             }; | ||||
|             SetTotpDependantActionsOnTask(task); | ||||
|  | ||||
|             task.ToIntent(i); | ||||
|             return i; | ||||
|         } | ||||
|  | ||||
|         private void SetTotpDependantActionsOnTask(SelectEntryTask task) | ||||
|         { | ||||
|             task.ShowUserNotifications = | ||||
|                 ShowNotificationPreference ? ActivationCondition.WhenTotp : ActivationCondition.Never; | ||||
|             task.CopyTotpToClipboard = CopyTotpToClipboardPreference; | ||||
|             task.ActivateKeyboard = ActivateKeyboardWhenTotpPreference | ||||
|                 ? ActivationCondition.WhenTotp | ||||
|                 : ActivationCondition.Never; | ||||
|         } | ||||
|  | ||||
|         protected override Intent GetOpenEntryIntent(string entryUuid) | ||||
|         { | ||||
|             Intent i = new Intent(this, typeof(SelectCurrentDbActivity)); | ||||
|             //don't show user notifications when an entry is opened. | ||||
|             var task = new OpenSpecificEntryTask() { EntryUuid = entryUuid }; | ||||
|             SetTotpDependantActionsOnTask(task); | ||||
|             task.ToIntent(i); | ||||
|             return i; | ||||
|         } | ||||
|   | ||||
| @@ -4,6 +4,7 @@ using System.Linq; | ||||
| using Android; | ||||
| using Android.App; | ||||
| using Android.Content; | ||||
| using Android.Preferences; | ||||
| using Android.Runtime; | ||||
| using keepass2android.services.AutofillBase; | ||||
| using keepass2android.services.AutofillBase.model; | ||||
| @@ -34,24 +35,29 @@ namespace keepass2android.services | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         protected override List<FilledAutofillFieldCollection<ViewNodeInputField>> GetSuggestedEntries(string query) | ||||
|         protected override Dictionary<PwEntryOutput, FilledAutofillFieldCollection<ViewNodeInputField>> GetSuggestedEntries(string query) | ||||
|         { | ||||
|             if (!App.Kp2a.DatabaseIsUnlocked) | ||||
|                 return new List<FilledAutofillFieldCollection<ViewNodeInputField>>(); | ||||
|                 return new Dictionary<PwEntryOutput, FilledAutofillFieldCollection<ViewNodeInputField>>(); | ||||
|             var foundEntries = (ShareUrlResults.GetSearchResultsForUrl(query)?.Entries ?? new PwObjectList<PwEntry>()) | ||||
|                 .Select(e => new PwEntryOutput(e, App.Kp2a.FindDatabaseForElement(e))) | ||||
|                 .ToList(); | ||||
|  | ||||
|             if (App.Kp2a.LastOpenedEntry?.SearchUrl == query) | ||||
|             { | ||||
|                 foundEntries.Clear(); | ||||
|                 foundEntries.Add(App.Kp2a.LastOpenedEntry); | ||||
|                 foundEntries.Add(App.Kp2a.LastOpenedEntry?.Entry); | ||||
|             } | ||||
|  | ||||
|             //it seems like at least with Firefox we can have at most 3 datasets. Reserve space for the disable/enable dataset and the "fill with KP2A" which allows to select another item | ||||
|             //so take only 1: | ||||
|             return foundEntries.Take(1).Select(e => ChooseForAutofillActivity.GetFilledAutofillFieldCollectionFromEntry(e, this)) | ||||
|                 .ToList(); | ||||
|             int numDisableDatasets = 0; | ||||
|             if (!PreferenceManager.GetDefaultSharedPreferences(this) | ||||
|                     .GetBoolean(GetString(Resource.String.NoAutofillDisabling_key), false)) | ||||
|                 numDisableDatasets = 1; | ||||
|  | ||||
|                 //it seems like at least with Firefox we can have at most 3 datasets. Reserve space for the disable dataset and the "fill with KP2A" which allows to select another item | ||||
|                 return foundEntries.Take(2-numDisableDatasets) | ||||
|                     .Select(e => new PwEntryOutput(e, App.Kp2a.FindDatabaseForElement(e))) | ||||
|                     .ToDictionary(e => e, | ||||
|                                 e => ChooseForAutofillActivity.GetFilledAutofillFieldCollectionFromEntry(e, this)); | ||||
|         } | ||||
|  | ||||
|         protected override void HandleSaveRequest(StructureParser parser, StructureParser.AutofillTargetId query) | ||||
|   | ||||
| @@ -9,6 +9,7 @@ using Android.Views; | ||||
| using Android.Widget; | ||||
| using keepass2android.services.AutofillBase; | ||||
| using keepass2android.services.Kp2aAutofill; | ||||
| using KeePassLib; | ||||
|  | ||||
| namespace keepass2android.services | ||||
| { | ||||
| @@ -29,16 +30,14 @@ namespace keepass2android.services | ||||
|             return PendingIntent.GetActivity(context, _pendingIntentRequestCode++, intent, Util.AddMutabilityFlag(PendingIntentFlags.CancelCurrent, PendingIntentFlags.Mutable)); | ||||
|         } | ||||
|  | ||||
|         public PendingIntent GetAuthPendingIntentForWarning(Context context, string query, string queryDomain, string queryPackage, | ||||
|         public PendingIntent GetAuthPendingIntentForWarning(Context context,PwUuid entryUuid, | ||||
|             AutofillServiceBase.DisplayWarning warning) | ||||
|         { | ||||
|             Intent intent = new Intent(context, typeof(ChooseForAutofillActivity)); | ||||
|             intent.PutExtra(ChooseForAutofillActivityBase.ExtraQueryString, query); | ||||
|             intent.PutExtra(ChooseForAutofillActivityBase.ExtraQueryDomainString, queryDomain); | ||||
|             intent.PutExtra(ChooseForAutofillActivityBase.ExtraQueryPackageString, queryPackage); | ||||
|             intent.PutExtra(ChooseForAutofillActivityBase.ExtraUuidString, entryUuid.ToHexString()); | ||||
|             intent.PutExtra(ChooseForAutofillActivityBase.ExtraDisplayWarning, (int)warning); | ||||
|             intent.PutExtra(ChooseForAutofillActivityBase.ExtraUseLastOpenedEntry, true); | ||||
|             return PendingIntent.GetActivity(context, _pendingIntentRequestCode++, intent, Util.AddMutabilityFlag(PendingIntentFlags.CancelCurrent, PendingIntentFlags.Immutable)); | ||||
|             return PendingIntent.GetActivity(context, _pendingIntentRequestCode++, intent, Util.AddMutabilityFlag(PendingIntentFlags.CancelCurrent, PendingIntentFlags.Mutable)); | ||||
|         } | ||||
|  | ||||
|         public PendingIntent GetDisablePendingIntentForResponse(Context context, string query, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 PhilippC
					PhilippC