mirror of
https://github.com/moparisthebest/keepass2android
synced 2025-01-11 05:28:34 -05:00
Inline Search with suggestions
This commit is contained in:
parent
4f49b073d8
commit
30cbc16051
@ -35,6 +35,11 @@ namespace KeePassLib
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class PwGroup : ITimeLogger, IStructureItem, IDeepCloneable<PwGroup>
|
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 DefaultAutoTypeEnabled = true;
|
||||||
public const bool DefaultSearchingEnabled = true;
|
public const bool DefaultSearchingEnabled = true;
|
||||||
|
|
||||||
@ -706,6 +711,18 @@ namespace KeePassLib
|
|||||||
/// be stored.</param>
|
/// be stored.</param>
|
||||||
public void SearchEntries(SearchParameters sp, PwObjectList<PwEntry> listStorage,
|
public void SearchEntries(SearchParameters sp, PwObjectList<PwEntry> listStorage,
|
||||||
IStatusLogger slStatus)
|
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(sp == null) { Debug.Assert(false); return; }
|
||||||
if(listStorage == 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((lTerms.Count <= 1) || sp.RegularExpression)
|
||||||
{
|
{
|
||||||
if(slStatus != null) uTotalEntries = GetEntriesCount(true);
|
if(slStatus != null) uTotalEntries = GetEntriesCount(true);
|
||||||
SearchEntriesSingle(sp, listStorage, slStatus, ref uCurEntries,
|
SearchEntriesSingle(sp, listStorage, resultContexts , slStatus, ref uCurEntries,
|
||||||
uTotalEntries);
|
uTotalEntries);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -748,7 +765,7 @@ namespace KeePassLib
|
|||||||
bNegate = (sp.SearchString.Length > 0);
|
bNegate = (sp.SearchString.Length > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!pg.SearchEntriesSingle(sp, pgNew.Entries, slStatus,
|
if(!pg.SearchEntriesSingle(sp, pgNew.Entries, resultContexts, slStatus,
|
||||||
ref uCurEntries, uTotalEntries))
|
ref uCurEntries, uTotalEntries))
|
||||||
{
|
{
|
||||||
pg = null;
|
pg = null;
|
||||||
@ -773,7 +790,7 @@ namespace KeePassLib
|
|||||||
}
|
}
|
||||||
|
|
||||||
private bool SearchEntriesSingle(SearchParameters spIn,
|
private bool SearchEntriesSingle(SearchParameters spIn,
|
||||||
PwObjectList<PwEntry> listStorage, IStatusLogger slStatus,
|
PwObjectList<PwEntry> listStorage, IDictionary<PwUuid, String> resultContexts, IStatusLogger slStatus,
|
||||||
ref ulong uCurEntries, ulong uTotalEntries)
|
ref ulong uCurEntries, ulong uTotalEntries)
|
||||||
{
|
{
|
||||||
SearchParameters sp = spIn.Clone();
|
SearchParameters sp = spIn.Clone();
|
||||||
@ -856,42 +873,42 @@ namespace KeePassLib
|
|||||||
if(strKey == PwDefs.TitleField)
|
if(strKey == PwDefs.TitleField)
|
||||||
{
|
{
|
||||||
if(bTitle) SearchEvalAdd(sp, kvp.Value.ReadString(),
|
if(bTitle) SearchEvalAdd(sp, kvp.Value.ReadString(),
|
||||||
rx, pe, listStorage);
|
rx, pe, listStorage, resultContexts, strKey);
|
||||||
}
|
}
|
||||||
else if(strKey == PwDefs.UserNameField)
|
else if(strKey == PwDefs.UserNameField)
|
||||||
{
|
{
|
||||||
if(bUserName) SearchEvalAdd(sp, kvp.Value.ReadString(),
|
if(bUserName) SearchEvalAdd(sp, kvp.Value.ReadString(),
|
||||||
rx, pe, listStorage);
|
rx, pe, listStorage, resultContexts, strKey);
|
||||||
}
|
}
|
||||||
else if(strKey == PwDefs.PasswordField)
|
else if(strKey == PwDefs.PasswordField)
|
||||||
{
|
{
|
||||||
if(bPassword) SearchEvalAdd(sp, kvp.Value.ReadString(),
|
if(bPassword) SearchEvalAdd(sp, kvp.Value.ReadString(),
|
||||||
rx, pe, listStorage);
|
rx, pe, listStorage, resultContexts, strKey);
|
||||||
}
|
}
|
||||||
else if(strKey == PwDefs.UrlField)
|
else if(strKey == PwDefs.UrlField)
|
||||||
{
|
{
|
||||||
if(bUrl) SearchEvalAdd(sp, kvp.Value.ReadString(),
|
if(bUrl) SearchEvalAdd(sp, kvp.Value.ReadString(),
|
||||||
rx, pe, listStorage);
|
rx, pe, listStorage, resultContexts, strKey);
|
||||||
}
|
}
|
||||||
else if(strKey == PwDefs.NotesField)
|
else if(strKey == PwDefs.NotesField)
|
||||||
{
|
{
|
||||||
if(bNotes) SearchEvalAdd(sp, kvp.Value.ReadString(),
|
if(bNotes) SearchEvalAdd(sp, kvp.Value.ReadString(),
|
||||||
rx, pe, listStorage);
|
rx, pe, listStorage, resultContexts, strKey);
|
||||||
}
|
}
|
||||||
else if(bOther)
|
else if(bOther)
|
||||||
SearchEvalAdd(sp, kvp.Value.ReadString(),
|
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
|
// An entry can match only once => break if we have added it
|
||||||
if(listStorage.UCount > uInitialResults) break;
|
if(listStorage.UCount > uInitialResults) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(bUuids && (listStorage.UCount == uInitialResults))
|
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) &&
|
if(bGroupName && (listStorage.UCount == uInitialResults) &&
|
||||||
(pe.ParentGroup != null))
|
(pe.ParentGroup != null))
|
||||||
SearchEvalAdd(sp, pe.ParentGroup.Name, rx, pe, listStorage);
|
SearchEvalAdd(sp, pe.ParentGroup.Name, rx, pe, listStorage, resultContexts, SearchContextParentGroup);
|
||||||
|
|
||||||
if(bTags)
|
if(bTags)
|
||||||
{
|
{
|
||||||
@ -899,7 +916,7 @@ namespace KeePassLib
|
|||||||
{
|
{
|
||||||
if(listStorage.UCount != uInitialResults) break; // Match
|
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,
|
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;
|
bool bMatch = false;
|
||||||
|
int matchPos;
|
||||||
|
|
||||||
if(rx == null)
|
if (rx == null)
|
||||||
bMatch = (strDataField.IndexOf(sp.SearchString,
|
{
|
||||||
sp.ComparisonMode) >= 0);
|
matchPos = strDataField.IndexOf(sp.SearchString, sp.ComparisonMode);
|
||||||
else bMatch = rx.IsMatch(strDataField);
|
bMatch = matchPos >= 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var match = rx.Match(strDataField);
|
||||||
|
bMatch = match.Success;
|
||||||
|
matchPos = match.Index;
|
||||||
|
}
|
||||||
|
|
||||||
if(!bMatch && (sp.DataTransformationFn != null))
|
if(!bMatch && (sp.DataTransformationFn != null))
|
||||||
{
|
{
|
||||||
string strCmp = sp.DataTransformationFn(strDataField, pe);
|
string strCmp = sp.DataTransformationFn(strDataField, pe);
|
||||||
if(!object.ReferenceEquals(strCmp, strDataField))
|
if(!object.ReferenceEquals(strCmp, strDataField))
|
||||||
{
|
{
|
||||||
if(rx == null)
|
if (rx == null)
|
||||||
bMatch = (strCmp.IndexOf(sp.SearchString,
|
{
|
||||||
sp.ComparisonMode) >= 0);
|
matchPos = strCmp.IndexOf(sp.SearchString, sp.ComparisonMode);
|
||||||
else bMatch = rx.IsMatch(strCmp);
|
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 = "… " + contextString.Substring(startPos, SearchContextStringMaxLength) + ((startPos + SearchContextStringMaxLength < contextString.Length) ? " …" : null);
|
||||||
|
}
|
||||||
|
resultContexts[pe.Uuid] = contextFieldName + ": " + contextString;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<string> BuildEntryTagsList()
|
public List<string> BuildEntryTagsList()
|
||||||
|
@ -125,7 +125,7 @@ namespace keepass2android
|
|||||||
entryId = new KeePassLib.PwUuid(MemUtil.HexStringToByteArray(uuidBytes));
|
entryId = new KeePassLib.PwUuid(MemUtil.HexStringToByteArray(uuidBytes));
|
||||||
|
|
||||||
State.parentGroup = null;
|
State.parentGroup = null;
|
||||||
if (entryId == PwUuid.Zero)
|
if (entryId.EqualsValue(PwUuid.Zero))
|
||||||
{
|
{
|
||||||
String groupId = i.GetStringExtra(KEY_PARENT);
|
String groupId = i.GetStringExtra(KEY_PARENT);
|
||||||
|
|
||||||
|
@ -197,7 +197,12 @@ namespace keepass2android
|
|||||||
|
|
||||||
MenuInflater inflater = MenuInflater;
|
MenuInflater inflater = MenuInflater;
|
||||||
inflater.Inflate(Resource.Menu.group, menu);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,8 +246,9 @@ namespace keepass2android
|
|||||||
SetResult(KeePass.EXIT_LOCK);
|
SetResult(KeePass.EXIT_LOCK);
|
||||||
Finish();
|
Finish();
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case Resource.Id.menu_search:
|
case Resource.Id.menu_search:
|
||||||
|
case Resource.Id.menu_search_advanced:
|
||||||
OnSearchRequested();
|
OnSearchRequested();
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
@ -20,6 +20,12 @@
|
|||||||
android:icon="@android:drawable/ic_menu_search"
|
android:icon="@android:drawable/ic_menu_search"
|
||||||
android:title="@string/menu_search"
|
android:title="@string/menu_search"
|
||||||
android:showAsAction="ifRoom"
|
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"
|
<item android:id="@+id/menu_lock"
|
||||||
android:icon="@android:drawable/ic_lock_lock"
|
android:icon="@android:drawable/ic_lock_lock"
|
||||||
|
@ -61,6 +61,7 @@
|
|||||||
<string name="entry_user_name">User Name</string>
|
<string name="entry_user_name">User Name</string>
|
||||||
<string name="entry_extra_strings">Extra string fields</string>
|
<string name="entry_extra_strings">Extra string fields</string>
|
||||||
<string name="entry_binaries">File attachments</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_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_can_not_handle_uri">Keepass2Android cannot handle this uri.</string>
|
||||||
<string name="error_could_not_create_group">Error creating group.</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_open">Open</string>
|
||||||
<string name="menu_rename">Rename</string>
|
<string name="menu_rename">Rename</string>
|
||||||
<string name="menu_search">Search</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="menu_url">Go to URL</string>
|
||||||
<string name="minus">Minus</string>
|
<string name="minus">Minus</string>
|
||||||
<string name="never">Never</string>
|
<string name="never">Never</string>
|
||||||
|
@ -21,4 +21,9 @@
|
|||||||
android:label="@string/search_label"
|
android:label="@string/search_label"
|
||||||
android:hint="@string/search_hint"
|
android:hint="@string/search_hint"
|
||||||
android:searchMode="showSearchLabelAsBadge"
|
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)
|
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);
|
return getIconDrawable (res, db, customIconId);
|
||||||
} else {
|
} else {
|
||||||
return getIconDrawable (res, icon);
|
return getIconDrawable (res, icon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void initBlank (Resources res)
|
private static void initBlank (Resources res)
|
||||||
{
|
{
|
||||||
if (blank == null) {
|
if (blank == null) {
|
||||||
@ -92,7 +92,7 @@ namespace keepass2android
|
|||||||
public Drawable getIconDrawable (Resources res, PwDatabase db, PwUuid icon)
|
public Drawable getIconDrawable (Resources res, PwDatabase db, PwUuid icon)
|
||||||
{
|
{
|
||||||
initBlank (res);
|
initBlank (res);
|
||||||
if (icon == PwUuid.Zero) {
|
if (icon.EqualsValue(PwUuid.Zero)) {
|
||||||
return blank;
|
return blank;
|
||||||
}
|
}
|
||||||
Drawable draw = null;
|
Drawable draw = null;
|
||||||
|
@ -86,6 +86,7 @@
|
|||||||
<Compile Include="Database.cs" />
|
<Compile Include="Database.cs" />
|
||||||
<Compile Include="fileselect\FileSelectActivity.cs" />
|
<Compile Include="fileselect\FileSelectActivity.cs" />
|
||||||
<Compile Include="fileselect\FileDbHelper.cs" />
|
<Compile Include="fileselect\FileDbHelper.cs" />
|
||||||
|
<Compile Include="search\SearchProvider.cs" />
|
||||||
<Compile Include="Utils\Util.cs" />
|
<Compile Include="Utils\Util.cs" />
|
||||||
<Compile Include="Utils\Interaction.cs" />
|
<Compile Include="Utils\Interaction.cs" />
|
||||||
<Compile Include="intents\Intents.cs" />
|
<Compile Include="intents\Intents.cs" />
|
||||||
|
@ -50,9 +50,9 @@ namespace keepass2android
|
|||||||
SearchParameters sp = new SearchParameters();
|
SearchParameters sp = new SearchParameters();
|
||||||
sp.SearchString = str;
|
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
|
if(sp.RegularExpression) // Validate regular expression
|
||||||
@ -67,7 +67,7 @@ namespace keepass2android
|
|||||||
PwObjectList<PwEntry> listResults = pgResults.Entries;
|
PwObjectList<PwEntry> listResults = pgResults.Entries;
|
||||||
|
|
||||||
|
|
||||||
database.root.SearchEntries(sp, listResults, new NullStatusLogger());
|
database.root.SearchEntries(sp, listResults, resultContexts, new NullStatusLogger());
|
||||||
|
|
||||||
|
|
||||||
return pgResults;
|
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 Android.Widget;
|
||||||
using keepass2android.view;
|
using keepass2android.view;
|
||||||
using KeePassLib;
|
using KeePassLib;
|
||||||
|
using Android.Support.V4.App;
|
||||||
|
|
||||||
namespace keepass2android.search
|
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")]
|
[MetaData("android.app.searchable",Resource="@xml/searchable")]
|
||||||
[IntentFilter(new[]{Intent.ActionSearch}, Categories=new[]{Intent.CategoryDefault})]
|
[IntentFilter(new[]{Intent.ActionSearch}, Categories=new[]{Intent.CategoryDefault})]
|
||||||
public class SearchResults : GroupBaseActivity
|
public class SearchResults : GroupBaseActivity
|
||||||
@ -46,20 +47,40 @@ namespace keepass2android.search
|
|||||||
}
|
}
|
||||||
|
|
||||||
SetResult(KeePass.EXIT_NORMAL);
|
SetResult(KeePass.EXIT_NORMAL);
|
||||||
|
|
||||||
|
ProcessIntent(Intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnNewIntent(Intent intent)
|
||||||
|
{
|
||||||
|
ProcessIntent(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessIntent(Intent intent)
|
||||||
|
{
|
||||||
mDb = App.getDB();
|
mDb = App.getDB();
|
||||||
|
|
||||||
// Likely the app has been killed exit the activity
|
// Likely the app has been killed exit the activity
|
||||||
if ( ! mDb.Open ) {
|
if (!mDb.Open)
|
||||||
|
{
|
||||||
Finish();
|
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)
|
private void query (SearchParameters searchParams)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
Loading…
Reference in New Issue
Block a user