Inline Search with suggestions
This commit is contained in:
		| @@ -35,6 +35,11 @@ namespace KeePassLib | ||||
| 	/// </summary> | ||||
| 	public sealed class PwGroup : ITimeLogger, IStructureItem, IDeepCloneable<PwGroup> | ||||
| 	{ | ||||
| 		private const int SearchContextStringMaxLength = 50; // Note, doesn't include elipsis, if added | ||||
| 		public const string SearchContextUuid = "Uuid"; | ||||
| 		public const string SearchContextParentGroup = "Parent Group"; | ||||
| 		public const string SearchContextTags = "Tags"; | ||||
|  | ||||
| 		public const bool DefaultAutoTypeEnabled = true; | ||||
| 		public const bool DefaultSearchingEnabled = true; | ||||
|  | ||||
| @@ -706,6 +711,18 @@ namespace KeePassLib | ||||
| 		/// be stored.</param> | ||||
| 		public void SearchEntries(SearchParameters sp, PwObjectList<PwEntry> listStorage, | ||||
| 			IStatusLogger slStatus) | ||||
| 		{ | ||||
| 		} | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Search this group and all subgroups for entries. | ||||
| 		/// </summary> | ||||
| 		/// <param name="sp">Specifies the search method.</param> | ||||
| 		/// <param name="listStorage">Entry list in which the search results will | ||||
| 		/// be stored.</param> | ||||
| 		/// <param name="resultContexts">Dictionary that will be populated with text fragments indicating the context of why each entry (keyed by Uuid) was returned</param> | ||||
| 		public void SearchEntries(SearchParameters sp, PwObjectList<PwEntry> listStorage, IDictionary<PwUuid, String> resultContexts, | ||||
| 			IStatusLogger slStatus) | ||||
| 		{ | ||||
| 			if(sp == null) { Debug.Assert(false); return; } | ||||
| 			if(listStorage == null) { Debug.Assert(false); return; } | ||||
| @@ -716,7 +733,7 @@ namespace KeePassLib | ||||
| 			if((lTerms.Count <= 1) || sp.RegularExpression) | ||||
| 			{ | ||||
| 				if(slStatus != null) uTotalEntries = GetEntriesCount(true); | ||||
| 				SearchEntriesSingle(sp, listStorage, slStatus, ref uCurEntries, | ||||
| 				SearchEntriesSingle(sp, listStorage, resultContexts , slStatus, ref uCurEntries, | ||||
| 					uTotalEntries); | ||||
| 				return; | ||||
| 			} | ||||
| @@ -748,7 +765,7 @@ namespace KeePassLib | ||||
| 					bNegate = (sp.SearchString.Length > 0); | ||||
| 				} | ||||
|  | ||||
| 				if(!pg.SearchEntriesSingle(sp, pgNew.Entries, slStatus, | ||||
| 				if(!pg.SearchEntriesSingle(sp, pgNew.Entries, resultContexts, slStatus, | ||||
| 					ref uCurEntries, uTotalEntries)) | ||||
| 				{ | ||||
| 					pg = null; | ||||
| @@ -773,7 +790,7 @@ namespace KeePassLib | ||||
| 		} | ||||
|  | ||||
| 		private bool SearchEntriesSingle(SearchParameters spIn, | ||||
| 			PwObjectList<PwEntry> listStorage, IStatusLogger slStatus, | ||||
| 			PwObjectList<PwEntry> listStorage, IDictionary<PwUuid, String> resultContexts, IStatusLogger slStatus, | ||||
| 			ref ulong uCurEntries, ulong uTotalEntries) | ||||
| 		{ | ||||
| 			SearchParameters sp = spIn.Clone(); | ||||
| @@ -856,42 +873,42 @@ namespace KeePassLib | ||||
| 						if(strKey == PwDefs.TitleField) | ||||
| 						{ | ||||
| 							if(bTitle) SearchEvalAdd(sp, kvp.Value.ReadString(), | ||||
| 								rx, pe, listStorage); | ||||
| 								rx, pe, listStorage, resultContexts, strKey); | ||||
| 						} | ||||
| 						else if(strKey == PwDefs.UserNameField) | ||||
| 						{ | ||||
| 							if(bUserName) SearchEvalAdd(sp, kvp.Value.ReadString(), | ||||
| 								rx, pe, listStorage); | ||||
| 								rx, pe, listStorage, resultContexts, strKey); | ||||
| 						} | ||||
| 						else if(strKey == PwDefs.PasswordField) | ||||
| 						{ | ||||
| 							if(bPassword) SearchEvalAdd(sp, kvp.Value.ReadString(), | ||||
| 								rx, pe, listStorage); | ||||
| 								rx, pe, listStorage, resultContexts, strKey); | ||||
| 						} | ||||
| 						else if(strKey == PwDefs.UrlField) | ||||
| 						{ | ||||
| 							if(bUrl) SearchEvalAdd(sp, kvp.Value.ReadString(), | ||||
| 								rx, pe, listStorage); | ||||
| 								rx, pe, listStorage, resultContexts, strKey); | ||||
| 						} | ||||
| 						else if(strKey == PwDefs.NotesField) | ||||
| 						{ | ||||
| 							if(bNotes) SearchEvalAdd(sp, kvp.Value.ReadString(), | ||||
| 								rx, pe, listStorage); | ||||
| 								rx, pe, listStorage, resultContexts, strKey); | ||||
| 						} | ||||
| 						else if(bOther) | ||||
| 							SearchEvalAdd(sp, kvp.Value.ReadString(), | ||||
| 								rx, pe, listStorage); | ||||
| 								rx, pe, listStorage, resultContexts, strKey); | ||||
|  | ||||
| 						// An entry can match only once => break if we have added it | ||||
| 						if(listStorage.UCount > uInitialResults) break; | ||||
| 					} | ||||
|  | ||||
| 					if(bUuids && (listStorage.UCount == uInitialResults)) | ||||
| 						SearchEvalAdd(sp, pe.Uuid.ToHexString(), rx, pe, listStorage); | ||||
| 						SearchEvalAdd(sp, pe.Uuid.ToHexString(), rx, pe, listStorage, resultContexts, SearchContextUuid); | ||||
|  | ||||
| 					if(bGroupName && (listStorage.UCount == uInitialResults) && | ||||
| 						(pe.ParentGroup != null)) | ||||
| 						SearchEvalAdd(sp, pe.ParentGroup.Name, rx, pe, listStorage); | ||||
| 						SearchEvalAdd(sp, pe.ParentGroup.Name, rx, pe, listStorage, resultContexts, SearchContextParentGroup); | ||||
|  | ||||
| 					if(bTags) | ||||
| 					{ | ||||
| @@ -899,7 +916,7 @@ namespace KeePassLib | ||||
| 						{ | ||||
| 							if(listStorage.UCount != uInitialResults) break; // Match | ||||
|  | ||||
| 							SearchEvalAdd(sp, strTag, rx, pe, listStorage); | ||||
| 							SearchEvalAdd(sp, strTag, rx, pe, listStorage, resultContexts, SearchContextTags); | ||||
| 						} | ||||
| 					} | ||||
|  | ||||
| @@ -913,28 +930,59 @@ namespace KeePassLib | ||||
| 		} | ||||
|  | ||||
| 		private static void SearchEvalAdd(SearchParameters sp, string strDataField, | ||||
| 			Regex rx, PwEntry pe, PwObjectList<PwEntry> lResults) | ||||
| 			Regex rx, PwEntry pe, PwObjectList<PwEntry> lResults, IDictionary<PwUuid, String> resultContexts, string contextFieldName) | ||||
| 		{ | ||||
| 			bool bMatch = false; | ||||
| 			int matchPos; | ||||
|  | ||||
| 			if(rx == null) | ||||
| 				bMatch = (strDataField.IndexOf(sp.SearchString, | ||||
| 					sp.ComparisonMode) >= 0); | ||||
| 			else bMatch = rx.IsMatch(strDataField); | ||||
| 			if (rx == null) | ||||
| 			{ | ||||
| 				matchPos = strDataField.IndexOf(sp.SearchString, sp.ComparisonMode); | ||||
| 				bMatch = matchPos >= 0; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				var match = rx.Match(strDataField); | ||||
| 				bMatch = match.Success; | ||||
| 				matchPos = match.Index; | ||||
| 			} | ||||
|  | ||||
| 			if(!bMatch && (sp.DataTransformationFn != null)) | ||||
| 			{ | ||||
| 				string strCmp = sp.DataTransformationFn(strDataField, pe); | ||||
| 				if(!object.ReferenceEquals(strCmp, strDataField)) | ||||
| 				{ | ||||
| 					if(rx == null) | ||||
| 						bMatch = (strCmp.IndexOf(sp.SearchString, | ||||
| 							sp.ComparisonMode) >= 0); | ||||
| 					else bMatch = rx.IsMatch(strCmp); | ||||
| 					if (rx == null) | ||||
| 					{ | ||||
| 						matchPos = strCmp.IndexOf(sp.SearchString, sp.ComparisonMode); | ||||
| 						bMatch = matchPos >= 0; | ||||
| 					} | ||||
| 					else | ||||
| 					{ | ||||
| 						var match = rx.Match(strCmp); | ||||
| 						bMatch = match.Success; | ||||
| 						matchPos = match.Index; | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if(bMatch) lResults.Add(pe); | ||||
| 			if (bMatch) | ||||
| 			{ | ||||
| 				lResults.Add(pe); | ||||
|  | ||||
| 				if (resultContexts != null) | ||||
| 				{ | ||||
| 					// Trim the value if necessary | ||||
| 					var contextString = strDataField; | ||||
| 					if (contextString.Length > SearchContextStringMaxLength) | ||||
| 					{ | ||||
| 						// Start 10% before actual data, and don't run over | ||||
| 						var startPos = Math.Min(matchPos - (SearchContextStringMaxLength / 10), contextString.Length - SearchContextStringMaxLength); | ||||
| 						contextString = "<22> " + contextString.Substring(startPos, SearchContextStringMaxLength) + ((startPos + SearchContextStringMaxLength < contextString.Length) ? " <20>" : null); | ||||
| 					} | ||||
| 					resultContexts[pe.Uuid] = contextFieldName + ": " + contextString; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		public List<string> BuildEntryTagsList() | ||||
|   | ||||
| @@ -125,7 +125,7 @@ namespace keepass2android | ||||
| 					entryId = new KeePassLib.PwUuid(MemUtil.HexStringToByteArray(uuidBytes)); | ||||
| 				 | ||||
| 				State.parentGroup = null; | ||||
| 				if (entryId == PwUuid.Zero) | ||||
| 				if (entryId.EqualsValue(PwUuid.Zero)) | ||||
| 				{ | ||||
| 					String groupId = i.GetStringExtra(KEY_PARENT); | ||||
| 					 | ||||
|   | ||||
| @@ -197,7 +197,12 @@ namespace keepass2android | ||||
| 			 | ||||
| 			MenuInflater inflater = MenuInflater; | ||||
| 			inflater.Inflate(Resource.Menu.group, menu); | ||||
|  | ||||
| 			var searchManager = (SearchManager)GetSystemService(Context.SearchService); | ||||
| 			var searchView = (SearchView)menu.FindItem(Resource.Id.menu_search).ActionView; | ||||
| 			 | ||||
| 			searchView.SetSearchableInfo(searchManager.GetSearchableInfo(ComponentName)); | ||||
|  | ||||
| 			return true; | ||||
| 		} | ||||
| 		 | ||||
| @@ -241,8 +246,9 @@ namespace keepass2android | ||||
| 				SetResult(KeePass.EXIT_LOCK); | ||||
| 				Finish(); | ||||
| 				return true; | ||||
| 				 | ||||
|  | ||||
| 			case Resource.Id.menu_search: | ||||
| 			case Resource.Id.menu_search_advanced: | ||||
| 				OnSearchRequested(); | ||||
| 				return true; | ||||
| 				 | ||||
|   | ||||
| @@ -20,6 +20,12 @@ | ||||
|         android:icon="@android:drawable/ic_menu_search" | ||||
|         android:title="@string/menu_search" | ||||
|         android:showAsAction="ifRoom" | ||||
|         android:actionViewClass="android.widget.SearchView"  | ||||
|     /> | ||||
|     <item android:id="@+id/menu_search_advanced" | ||||
|         android:icon="@android:drawable/ic_menu_search" | ||||
|         android:title="@string/menu_search_advanced" | ||||
|         android:showAsAction="never" | ||||
|     /> | ||||
|     <item android:id="@+id/menu_lock" | ||||
|         android:icon="@android:drawable/ic_lock_lock" | ||||
|   | ||||
| @@ -61,6 +61,7 @@ | ||||
|   <string name="entry_user_name">User Name</string> | ||||
|   <string name="entry_extra_strings">Extra string fields</string> | ||||
|   <string name="entry_binaries">File attachments</string> | ||||
|   <string name="entry_notes">Notes</string> | ||||
|   <string name="error_arc4">The ArcFour stream cipher is not supported.</string> | ||||
|   <string name="error_can_not_handle_uri">Keepass2Android cannot handle this uri.</string> | ||||
|   <string name="error_could_not_create_group">Error creating group.</string> | ||||
| @@ -129,6 +130,7 @@ | ||||
|   <string name="menu_open">Open</string> | ||||
|   <string name="menu_rename">Rename</string> | ||||
|   <string name="menu_search">Search</string> | ||||
|   <string name="menu_search_advanced">Advanced Search</string> | ||||
|   <string name="menu_url">Go to URL</string> | ||||
|   <string name="minus">Minus</string> | ||||
|   <string name="never">Never</string> | ||||
|   | ||||
| @@ -21,4 +21,9 @@ | ||||
|     android:label="@string/search_label" | ||||
|     android:hint="@string/search_hint"  | ||||
|     android:searchMode="showSearchLabelAsBadge" | ||||
|     android:searchSuggestAuthority="keepass2android.search.SearchProvider" | ||||
|     android:searchSuggestSelection=" ?" | ||||
|     android:searchSuggestThreshold="2" | ||||
|     android:searchSuggestIntentAction="android.intent.action.VIEW" | ||||
|     android:searchSuggestIntentData="content://keepass2android.EntryActivity" | ||||
|  /> | ||||
| @@ -59,13 +59,13 @@ namespace keepass2android | ||||
| 			 | ||||
| 		public Drawable getIconDrawable (Resources res, PwDatabase db, PwIcon icon, PwUuid customIconId) | ||||
| 		{ | ||||
| 			if (customIconId != PwUuid.Zero) { | ||||
| 			if (!customIconId.EqualsValue(PwUuid.Zero)) { | ||||
| 				return getIconDrawable (res, db, customIconId); | ||||
| 			} else { | ||||
| 				return getIconDrawable (res, icon); | ||||
| 			} | ||||
| 		} | ||||
| 			 | ||||
|  | ||||
| 		private static void initBlank (Resources res)	 | ||||
| 		{ | ||||
| 			if (blank == null) { | ||||
| @@ -92,7 +92,7 @@ namespace keepass2android | ||||
| 		public Drawable getIconDrawable (Resources res, PwDatabase db, PwUuid icon) | ||||
| 		{ | ||||
| 			initBlank (res); | ||||
| 			if (icon == PwUuid.Zero) { | ||||
| 			if (icon.EqualsValue(PwUuid.Zero)) { | ||||
| 				return blank; | ||||
| 			} | ||||
| 			Drawable draw = null; | ||||
|   | ||||
| @@ -86,6 +86,7 @@ | ||||
|     <Compile Include="Database.cs" /> | ||||
|     <Compile Include="fileselect\FileSelectActivity.cs" /> | ||||
|     <Compile Include="fileselect\FileDbHelper.cs" /> | ||||
|     <Compile Include="search\SearchProvider.cs" /> | ||||
|     <Compile Include="Utils\Util.cs" /> | ||||
|     <Compile Include="Utils\Interaction.cs" /> | ||||
|     <Compile Include="intents\Intents.cs" /> | ||||
|   | ||||
| @@ -50,9 +50,9 @@ namespace keepass2android | ||||
| 			SearchParameters sp = new SearchParameters(); | ||||
| 			sp.SearchString = str; | ||||
|  | ||||
| 			return search(database, sp); | ||||
| 			return search(database, sp, null); | ||||
| 		} | ||||
| 		public PwGroup search(Database database, SearchParameters sp) | ||||
| 		public PwGroup search(Database database, SearchParameters sp, IDictionary<PwUuid, String> resultContexts) | ||||
| 		{ | ||||
| 			 | ||||
| 			if(sp.RegularExpression) // Validate regular expression | ||||
| @@ -67,7 +67,7 @@ namespace keepass2android | ||||
| 			PwObjectList<PwEntry> listResults = pgResults.Entries; | ||||
| 			 | ||||
| 			 | ||||
| 			database.root.SearchEntries(sp, listResults, new NullStatusLogger()); | ||||
| 			database.root.SearchEntries(sp, listResults, resultContexts, new NullStatusLogger()); | ||||
| 			 | ||||
| 			 | ||||
| 			return pgResults; | ||||
|   | ||||
							
								
								
									
										309
									
								
								src/keepass2android/search/SearchProvider.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										309
									
								
								src/keepass2android/search/SearchProvider.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,309 @@ | ||||
| /* | ||||
| This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin. | ||||
|  | ||||
|   Keepass2Android is free software: you can redistribute it and/or modify | ||||
|   it under the terms of the GNU General Public License as published by | ||||
|   the Free Software Foundation, either version 2 of the License, or | ||||
|   (at your option) any later version. | ||||
|  | ||||
|   Keepass2Android is distributed in the hope that it will be useful, | ||||
|   but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|   GNU General Public License for more details. | ||||
|  | ||||
|   You should have received a copy of the GNU General Public License | ||||
|   along with Keepass2Android.  If not, see <http://www.gnu.org/licenses/>. | ||||
|   */ | ||||
| using System; | ||||
| using System.Linq; | ||||
| using Android.App; | ||||
| using Android.Content; | ||||
| using Android.Content.Res; | ||||
| using Android.Database; | ||||
| using Android.Graphics; | ||||
| using Android.Graphics.Drawables; | ||||
| using Android.OS; | ||||
| using Android.Runtime; | ||||
|  | ||||
| using KeePassLib; | ||||
| using KeePassLib.Utility; | ||||
| using System.Threading; | ||||
| using System.Collections.Generic; | ||||
|  | ||||
| namespace keepass2android.search | ||||
| { | ||||
| 	[ContentProvider(new [] { SearchProvider.Authority })] | ||||
| 	public class SearchProvider : ContentProvider | ||||
| 	{ | ||||
| 		private enum UriMatches | ||||
| 		{ | ||||
| 			NoMatch = UriMatcher.NoMatch, | ||||
| 			GetIcon, | ||||
| 			GetSuggestions | ||||
| 		} | ||||
| 		public const string Authority = "keepass2android.search.SearchProvider"; | ||||
| 		 | ||||
| 		private const string GetIconPathQuery = "get_icon"; | ||||
| 		private const string IconIdParameter = "IconId"; | ||||
| 		private const string CustomIconUuidParameter = "CustomIconUuid"; | ||||
| 		//public static readonly String AUTHORITY = "keepass2android.search.SearchProvider"; | ||||
| 		//public static readonly Android.Net.Uri CONTENT_URI = Android.Net.Uri.Parse("content://" + AUTHORITY + "/dictionary"); | ||||
|  | ||||
| 		private Database mDb; | ||||
|  | ||||
| 		private static UriMatcher UriMatcher = BuildUriMatcher(); | ||||
|  | ||||
| 		static UriMatcher BuildUriMatcher() | ||||
| 		{ | ||||
| 			var matcher = new UriMatcher(UriMatcher.NoMatch); | ||||
|  | ||||
| 			// to get definitions... | ||||
| 			matcher.AddURI(Authority, GetIconPathQuery, (int)UriMatches.GetIcon); | ||||
| 			matcher.AddURI(Authority, SearchManager.SuggestUriPathQuery, (int)UriMatches.GetSuggestions); | ||||
|  | ||||
| 			return matcher; | ||||
| 		} | ||||
|  | ||||
| 		public override bool OnCreate() | ||||
| 		{ | ||||
| 			mDb = App.getDB(); | ||||
| 			return true; | ||||
| 		} | ||||
|  | ||||
| 		public override Android.Database.ICursor Query(Android.Net.Uri uri, string[] projection, string selection, string[] selectionArgs, string sortOrder) | ||||
| 		{ | ||||
| 			if (mDb.Open) // Can't show suggestions if the database is locked! | ||||
| 			{ | ||||
| 				switch ((UriMatches)UriMatcher.Match(uri)) | ||||
| 				{ | ||||
| 					case UriMatches.GetSuggestions: | ||||
| 						var searchString = selectionArgs[0]; | ||||
| 						if (!String.IsNullOrEmpty(searchString)) | ||||
| 						{ | ||||
| 							try | ||||
| 							{ | ||||
| 								var resultsContexts = new Dictionary<PwUuid, String>(); | ||||
| 								var result = mDb.Search(new SearchParameters { SearchString = searchString }, resultsContexts ); | ||||
| 								return new GroupCursor(result, resultsContexts); | ||||
| 							} | ||||
| 							catch (Exception e) | ||||
| 							{ | ||||
| 								System.Diagnostics.Debug.WriteLine("Failed to search for suggestions: " + e.Message); | ||||
| 							} | ||||
| 						} | ||||
| 						break; | ||||
| 					case UriMatches.GetIcon: | ||||
| 						return null; // This will be handled by OpenAssetFile | ||||
|  | ||||
| 					default: | ||||
| 						return null; | ||||
| 						//throw new ArgumentException("Unknown Uri: " + uri, "uri"); | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			return null; | ||||
| 		} | ||||
|  | ||||
| 		public override ParcelFileDescriptor OpenFile(Android.Net.Uri uri, string mode) | ||||
| 		{ | ||||
| 			switch ((UriMatches)UriMatcher.Match(uri)) | ||||
| 			{ | ||||
| 				case UriMatches.GetIcon: | ||||
| 					var iconId = (PwIcon)Enum.Parse(typeof(PwIcon), uri.GetQueryParameter(IconIdParameter)); | ||||
| 					var customIconUuid = new PwUuid(MemUtil.HexStringToByteArray(uri.GetQueryParameter(CustomIconUuidParameter))); | ||||
|  | ||||
| 					var iconDrawable = mDb.drawFactory.getIconDrawable(App.Context.Resources, mDb.pm, iconId, customIconUuid) as BitmapDrawable; | ||||
| 					if (iconDrawable != null) | ||||
| 					{ | ||||
| 						var pipe = ParcelFileDescriptor.CreatePipe(); | ||||
| 						var outStream = new OutputStreamInvoker(new ParcelFileDescriptor.AutoCloseOutputStream(pipe[1])); | ||||
|  | ||||
| 						ThreadPool.QueueUserWorkItem(state => | ||||
| 							{ | ||||
| 								iconDrawable.Bitmap.Compress(Bitmap.CompressFormat.Png, 100, outStream); | ||||
| 								outStream.Close(); | ||||
| 							}); | ||||
| 						 | ||||
| 						return pipe[0]; | ||||
| 					} | ||||
|  | ||||
| 					// Couldn't get an icon for some reason. | ||||
| 					return null; | ||||
| 				default: | ||||
| 					throw new ArgumentException("Unknown Uri: " + uri, "uri"); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		public override string GetType(Android.Net.Uri uri) | ||||
| 		{ | ||||
| 			switch ((UriMatches)UriMatcher.Match(uri)) | ||||
| 			{ | ||||
| 				case UriMatches.GetSuggestions: | ||||
| 					return SearchManager.SuggestMimeType; | ||||
| 				case UriMatches.GetIcon: | ||||
| 					return "image/png"; | ||||
|  | ||||
| 				default: | ||||
| 					throw new ArgumentException("Unknown Uri: " + uri, "uri"); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		#region Unimplemented | ||||
| 		public override int Delete(Android.Net.Uri uri, string selection, string[] selectionArgs) | ||||
| 		{ | ||||
| 			throw new NotImplementedException(); | ||||
| 		} | ||||
| 		public override Android.Net.Uri Insert(Android.Net.Uri uri, ContentValues values) | ||||
| 		{ | ||||
| 			throw new NotImplementedException(); | ||||
| 		} | ||||
| 		public override int Update(Android.Net.Uri uri, ContentValues values, string selection, string[] selectionArgs) | ||||
| 		{ | ||||
| 			throw new NotImplementedException(); | ||||
| 		} | ||||
| 		#endregion | ||||
|  | ||||
|  | ||||
| 		private class GroupCursor : AbstractCursor | ||||
| 		{ | ||||
| 			private static readonly string[] ColumnNames = new[] { Android.Provider.BaseColumns.Id,  | ||||
| 																	SearchManager.SuggestColumnText1,  | ||||
| 																	SearchManager.SuggestColumnText2,  | ||||
| 																	SearchManager.SuggestColumnIcon1, | ||||
| 																	SearchManager.SuggestColumnIntentDataId, | ||||
| 			}; | ||||
|  | ||||
| 			private readonly PwGroup mGroup; | ||||
| 			private readonly IDictionary<PwUuid, String> mResultContexts; | ||||
|  | ||||
| 			public GroupCursor(PwGroup group, IDictionary<PwUuid, String> resultContexts) | ||||
| 			{ | ||||
| 				System.Diagnostics.Debug.Assert(!group.Groups.Any(), "Expecting a flat list of groups"); | ||||
|  | ||||
| 				mGroup = group; | ||||
| 				mResultContexts = resultContexts; | ||||
| 			} | ||||
|  | ||||
| 			public override int Count | ||||
| 			{ | ||||
| 				get { return (int)Math.Min(mGroup.GetEntriesCount(false), int.MaxValue); } | ||||
| 			} | ||||
|  | ||||
| 			public override string[] GetColumnNames() | ||||
| 			{ | ||||
| 				return ColumnNames; | ||||
| 			} | ||||
|  | ||||
| 			public override FieldType GetType(int column) | ||||
| 			{ | ||||
| 				switch (column) | ||||
| 				{ | ||||
| 					case 0: // _ID | ||||
| 						return FieldType.Integer; | ||||
| 					default: | ||||
| 						return base.GetType(column); // Ends up as string | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			private PwEntry CurrentEntry | ||||
| 			{ | ||||
| 				get | ||||
| 				{ | ||||
| 					return mGroup.Entries.GetAt((uint)MPos); | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			public override long GetLong(int column) | ||||
| 			{ | ||||
| 				switch (column) | ||||
| 				{ | ||||
| 					case 0: // _ID | ||||
| 						return MPos; | ||||
| 					default: | ||||
| 						throw new FormatException(); | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			public override string GetString(int column) | ||||
| 			{ | ||||
| 				switch (column) | ||||
| 				{ | ||||
| 					case 0: // _ID | ||||
| 						return MPos.ToString(); | ||||
| 					case 1: // SuggestColumnText1 | ||||
| 						return CurrentEntry.Strings.ReadSafe(PwDefs.TitleField); | ||||
| 					case 2: // SuggestColumnText2 | ||||
| 						string context; | ||||
| 						if (mResultContexts.TryGetValue(CurrentEntry.Uuid, out context)) | ||||
| 						{ | ||||
| 							context = Internationalise(context); | ||||
| 							return context; | ||||
| 						} | ||||
| 						return null; | ||||
| 					case 3: // SuggestColumnIcon1 | ||||
| 						var builder = new Android.Net.Uri.Builder(); | ||||
| 						builder.Scheme(ContentResolver.SchemeContent); | ||||
| 						builder.Authority(Authority); | ||||
| 						builder.Path(GetIconPathQuery); | ||||
| 						builder.AppendQueryParameter(IconIdParameter, CurrentEntry.IconId.ToString()); | ||||
| 						builder.AppendQueryParameter(CustomIconUuidParameter, CurrentEntry.CustomIconUuid.ToHexString()); | ||||
| 						return builder.Build().ToString(); | ||||
| 					case 4: // SuggestColumnIntentDataId | ||||
| 						return CurrentEntry.Uuid.ToHexString(); | ||||
| 					default: | ||||
| 						return null; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			private string Internationalise(string context) | ||||
| 			{ | ||||
| 				// Some context names can be internationalised. | ||||
| 				var splitPos = context.IndexOf(':'); | ||||
| 				var rawName = context.Substring(0, splitPos); | ||||
| 				int intlResourceId = 0; | ||||
| 				switch (rawName) | ||||
| 				{ | ||||
| 					case PwDefs.TitleField: | ||||
| 						// We will already be showing Title, so ignore it entirely so it doesn't double-appear | ||||
| 						return null; | ||||
| 					case PwDefs.UserNameField: | ||||
| 						intlResourceId = Resource.String.entry_user_name; | ||||
| 						break; | ||||
| 					case PwDefs.UrlField: | ||||
| 						intlResourceId = Resource.String.entry_url; | ||||
| 						break; | ||||
| 					case PwDefs.NotesField: | ||||
| 						intlResourceId = Resource.String.entry_notes; | ||||
| 						break; | ||||
| 					case PwGroup.SearchContextTags: | ||||
| 						intlResourceId = Resource.String.entry_tags; | ||||
| 						break; | ||||
| 					default: | ||||
| 						// Other fields aren't part of the default SearchParameters, so we won't ever get them as context anyway | ||||
| 						break; | ||||
| 				} | ||||
|  | ||||
| 				if (intlResourceId > 0) | ||||
| 				{ | ||||
| 					return App.Context.GetString(intlResourceId) + context.Substring(splitPos); | ||||
| 				} | ||||
|  | ||||
| 				return context; | ||||
| 			} | ||||
|  | ||||
| 			public override bool IsNull(int column) | ||||
| 			{ | ||||
| 				return false; | ||||
| 			} | ||||
|  | ||||
| 			#region Data types appearing in no columns | ||||
| 			public override int GetInt(int column) { throw new FormatException(); } | ||||
| 			public override double GetDouble(int column) { throw new FormatException(); } | ||||
| 			public override float GetFloat(int column) { throw new FormatException(); } | ||||
| 			public override short GetShort(int column) { throw new FormatException(); } | ||||
| 			#endregion | ||||
| 		} | ||||
|  | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -27,10 +27,11 @@ using Android.Views; | ||||
| using Android.Widget; | ||||
| using keepass2android.view; | ||||
| using KeePassLib; | ||||
| using Android.Support.V4.App; | ||||
|  | ||||
| namespace keepass2android.search | ||||
| { | ||||
| 	[Activity (Label = "@string/app_name", Theme="@style/NoTitleBar")] | ||||
| 	[Activity (Label = "@string/app_name", Theme="@style/NoTitleBar", LaunchMode=Android.Content.PM.LaunchMode.SingleTop)] | ||||
| 	[MetaData("android.app.searchable",Resource="@xml/searchable")] | ||||
| 	[IntentFilter(new[]{Intent.ActionSearch}, Categories=new[]{Intent.CategoryDefault})] | ||||
| 	public class SearchResults : GroupBaseActivity | ||||
| @@ -46,20 +47,40 @@ namespace keepass2android.search | ||||
| 			} | ||||
| 			 | ||||
| 			SetResult(KeePass.EXIT_NORMAL); | ||||
| 			 | ||||
|  | ||||
| 			ProcessIntent(Intent); | ||||
| 		} | ||||
|  | ||||
| 		protected override void OnNewIntent(Intent intent) | ||||
| 		{ | ||||
| 			ProcessIntent(intent); | ||||
| 		} | ||||
|  | ||||
| 		private void ProcessIntent(Intent intent) | ||||
| 		{ | ||||
| 			mDb = App.getDB(); | ||||
| 			 | ||||
|  | ||||
| 			// Likely the app has been killed exit the activity  | ||||
| 			if ( ! mDb.Open ) { | ||||
| 			if (!mDb.Open) | ||||
| 			{ | ||||
| 				Finish(); | ||||
| 			} | ||||
|  | ||||
| 			query(getSearch(Intent)); | ||||
|  | ||||
| 			if (intent.Action == Intent.ActionView) | ||||
| 			{ | ||||
| 				var entryIntent = new Intent(this, typeof(EntryActivity)); | ||||
| 				entryIntent.PutExtra(EntryActivity.KEY_ENTRY, intent.Data.LastPathSegment); | ||||
|  | ||||
| 				Finish(); // Close this activity so that the entry activity is navigated to from the main activity, not this one. | ||||
| 				StartActivity(entryIntent); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				// Action may either by ActionSearch (from search widget) or null (if called from SearchActivity directly) | ||||
| 				query(getSearch(intent)); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
|  | ||||
| 		private void query (SearchParameters searchParams) | ||||
| 		{ | ||||
| 			try { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 AlexVallat
					AlexVallat