Methods Summary |
---|
public static void | buildSearchableList(android.content.Context context)Super-factory. Builds an entire list (suitable for display) of
activities that are searchable, by iterating the entire set of
ACTION_SEARCH intents.
Also clears the hash of all activities -> searches which will
refill as the user clicks "search".
This should only be done at startup and again if we know that the
list has changed.
TODO: every activity that provides a ACTION_SEARCH intent should
also provide searchability meta-data. There are a bunch of checks here
that, if data is not found, silently skip to the next activity. This
won't help a developer trying to figure out why their activity isn't
showing up in the list, but an exception here is too rough. I would
like to find a better notification mechanism.
TODO: sort the list somehow? UI choice.
// create empty hash & list
HashMap<ComponentName, SearchableInfo> newSearchablesMap
= new HashMap<ComponentName, SearchableInfo>();
ArrayList<SearchableInfo> newSearchablesList
= new ArrayList<SearchableInfo>();
// use intent resolver to generate list of ACTION_SEARCH receivers
final PackageManager pm = context.getPackageManager();
List<ResolveInfo> infoList;
final Intent intent = new Intent(Intent.ACTION_SEARCH);
infoList = pm.queryIntentActivities(intent, PackageManager.GET_META_DATA);
// analyze each one, generate a Searchables record, and record
if (infoList != null) {
int count = infoList.size();
for (int ii = 0; ii < count; ii++) {
// for each component, try to find metadata
ResolveInfo info = infoList.get(ii);
ActivityInfo ai = info.activityInfo;
XmlResourceParser xml = ai.loadXmlMetaData(context.getPackageManager(),
MD_LABEL_SEARCHABLE);
if (xml == null) {
continue;
}
ComponentName cName = new ComponentName(
info.activityInfo.packageName,
info.activityInfo.name);
SearchableInfo searchable = getActivityMetaData(context, xml, cName);
xml.close();
if (searchable != null) {
// no need to keep the context any longer. setup time is over.
searchable.mCacheActivityContext = null;
newSearchablesList.add(searchable);
newSearchablesMap.put(cName, searchable);
}
}
}
// record the final values as a coherent pair
synchronized (SearchableInfo.class) {
sSearchablesList = newSearchablesList;
sSearchablesMap = newSearchablesMap;
}
|
public int | describeContents()
return 0;
|
public android.server.search.SearchableInfo$ActionKeyInfo | findActionKey(int keyCode)If any action keys were defined for this searchable activity, look up and return.
ActionKeyInfo info = mActionKeyList;
while (info != null) {
if (info.mKeyCode == keyCode) {
return info;
}
info = info.mNext;
}
return null;
|
public android.content.Context | getActivityContext(android.content.Context context)Get the context for the searchable activity.
This is fairly expensive so do it on the original scan, or when an app is
selected, but don't hang on to the result forever.
Context theirContext = null;
try {
theirContext = context.createPackageContext(mSearchActivity.getPackageName(), 0);
} catch (PackageManager.NameNotFoundException e) {
// unexpected, but we deal with this by null-checking theirContext
} catch (java.lang.SecurityException e) {
// unexpected, but we deal with this by null-checking theirContext
}
return theirContext;
|
private static android.server.search.SearchableInfo | getActivityMetaData(android.content.Context context, org.xmlpull.v1.XmlPullParser xml, android.content.ComponentName cName)Get the metadata for a given activity
TODO: clean up where we return null vs. where we throw exceptions.
SearchableInfo result = null;
// in order to use the attributes mechanism, we have to walk the parser
// forward through the file until it's reading the tag of interest.
try {
int tagType = xml.next();
while (tagType != XmlPullParser.END_DOCUMENT) {
if (tagType == XmlPullParser.START_TAG) {
if (xml.getName().equals(MD_XML_ELEMENT_SEARCHABLE)) {
AttributeSet attr = Xml.asAttributeSet(xml);
if (attr != null) {
result = new SearchableInfo(context, attr, cName);
// if the constructor returned a bad object, exit now.
if (! result.mSearchable) {
return null;
}
}
} else if (xml.getName().equals(MD_XML_ELEMENT_SEARCHABLE_ACTION_KEY)) {
if (result == null) {
// Can't process an embedded element if we haven't seen the enclosing
return null;
}
AttributeSet attr = Xml.asAttributeSet(xml);
if (attr != null) {
ActionKeyInfo keyInfo = result.new ActionKeyInfo(context, attr,
result.mActionKeyList);
// only add to list if it is was useable
if (keyInfo.mKeyCode != 0) {
result.mActionKeyList = keyInfo;
}
}
}
}
tagType = xml.next();
}
} catch (XmlPullParserException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
return result;
|
public static android.server.search.SearchableInfo | getDefaultSearchable()Provides the system-default search activity, which you can use
whenever getSearchableInfo() returns null;
synchronized (SearchableInfo.class) {
return sDefaultSearchable;
}
|
public int | getHintId()Return the resource Id of the hint text. This must be
accessed using the target (searchable) Activity's resources, not simply the context of the
caller.
return mHintId;
|
public int | getIconId()Return the icon Id specified by the Searchable_icon meta-data entry. This must be
accessed using the target (searchable) Activity's resources, not simply the context of the
caller.
return mIconId;
|
public int | getImeOptions()Return the input method options specified in the searchable attributes.
This will default to EditorInfo.ACTION_SEARCH if not specified (which is
appropriate for a search box).
return mSearchImeOptions;
|
public int | getInputType()Return the input type as specified in the searchable attributes. This will default to
InputType.TYPE_CLASS_TEXT if not specified (which is appropriate for free text input).
return mSearchInputType;
|
public int | getLabelId()Return the "label" (user-visible name) of this searchable context. This must be
accessed using the target (searchable) Activity's resources, not simply the context of the
caller.
return mLabelId;
|
public android.content.Context | getProviderContext(android.content.Context context, android.content.Context activityContext)Get the context for the suggestions provider.
This is fairly expensive so do it on the original scan, or when an app is
selected, but don't hang on to the result forever.
Context theirContext = null;
if (mSearchActivity.getPackageName().equals(mSuggestProviderPackage)) {
return activityContext;
}
if (mSuggestProviderPackage != null)
try {
theirContext = context.createPackageContext(mSuggestProviderPackage, 0);
} catch (PackageManager.NameNotFoundException e) {
// unexpected, but we deal with this by null-checking theirContext
} catch (java.lang.SecurityException e) {
// unexpected, but we deal with this by null-checking theirContext
}
return theirContext;
|
public int | getSearchButtonText()Return the resource Id of replacement text for the "Search" button.
return mSearchButtonText;
|
public static android.server.search.SearchableInfo | getSearchableInfo(android.content.Context context, android.content.ComponentName activity)Factory. Look up, or construct, based on the activity.
The activities fall into three cases, based on meta-data found in
the manifest entry:
- The activity itself implements search. This is indicated by the
presence of a "android.app.searchable" meta-data attribute.
The value is a reference to an XML file containing search information.
- A related activity implements search. This is indicated by the
presence of a "android.app.default_searchable" meta-data attribute.
The value is a string naming the activity implementing search. In this
case the factory will "redirect" and return the searchable data.
- No searchability data is provided. We return null here and other
code will insert the "default" (e.g. contacts) search.
TODO: cache the result in the map, and check the map first.
TODO: it might make sense to implement the searchable reference as
an application meta-data entry. This way we don't have to pepper each
and every activity.
TODO: can we skip the constructor step if it's a non-searchable?
TODO: does it make sense to plug the default into a slot here for
automatic return? Probably not, but it's one way to do it.
// Step 1. Is the result already hashed? (case 1)
SearchableInfo result;
synchronized (SearchableInfo.class) {
result = sSearchablesMap.get(activity);
if (result != null) return result;
}
// Step 2. See if the current activity references a searchable.
// Note: Conceptually, this could be a while(true) loop, but there's
// no point in implementing reference chaining here and risking a loop.
// References must point directly to searchable activities.
ActivityInfo ai = null;
XmlPullParser xml = null;
try {
ai = context.getPackageManager().
getActivityInfo(activity, PackageManager.GET_META_DATA );
String refActivityName = null;
// First look for activity-specific reference
Bundle md = ai.metaData;
if (md != null) {
refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE);
}
// If not found, try for app-wide reference
if (refActivityName == null) {
md = ai.applicationInfo.metaData;
if (md != null) {
refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE);
}
}
// Irrespective of source, if a reference was found, follow it.
if (refActivityName != null)
{
// An app or activity can declare that we should simply launch
// "system default search" if search is invoked.
if (refActivityName.equals(MD_SEARCHABLE_SYSTEM_SEARCH)) {
return getDefaultSearchable();
}
String pkg = activity.getPackageName();
ComponentName referredActivity;
if (refActivityName.charAt(0) == '.") {
referredActivity = new ComponentName(pkg, pkg + refActivityName);
} else {
referredActivity = new ComponentName(pkg, refActivityName);
}
// Now try the referred activity, and if found, cache
// it against the original name so we can skip the check
synchronized (SearchableInfo.class) {
result = sSearchablesMap.get(referredActivity);
if (result != null) {
sSearchablesMap.put(activity, result);
return result;
}
}
}
} catch (PackageManager.NameNotFoundException e) {
// case 3: no metadata
}
// Step 3. None found. Return null.
return null;
|
public static java.util.ArrayList | getSearchablesList()Return the list of searchable activities, for use in the drop-down.
synchronized (SearchableInfo.class) {
ArrayList<SearchableInfo> result = new ArrayList<SearchableInfo>(sSearchablesList);
return result;
}
|
public java.lang.String | getSuggestAuthority()Retrieve the authority for obtaining search suggestions.
return mSuggestAuthority;
|
public java.lang.String | getSuggestIntentAction()Retrieve the (optional) intent action for use with these suggestions. This is
useful if all intents will have the same action (e.g. "android.intent.action.VIEW").
Can be overriden in any given suggestion via the AUTOSUGGEST_COLUMN_INTENT_ACTION column.
return mSuggestIntentAction;
|
public java.lang.String | getSuggestIntentData()Retrieve the (optional) intent data for use with these suggestions. This is
useful if all intents will have similar data URIs (e.g. "android.intent.action.VIEW"),
but you'll likely need to provide a specific ID as well via the column
AUTOSUGGEST_COLUMN_INTENT_DATA_ID, which will be appended to the intent data URI.
Can be overriden in any given suggestion via the AUTOSUGGEST_COLUMN_INTENT_DATA column.
return mSuggestIntentData;
|
public java.lang.String | getSuggestPath()Retrieve the path for obtaining search suggestions.
return mSuggestPath;
|
public java.lang.String | getSuggestSelection()Retrieve the selection pattern for obtaining search suggestions. This must
include a single ? which will be used for the user-typed characters.
return mSuggestSelection;
|
public int | getVoiceLanguageId()
return mVoiceLanguageId;
|
public int | getVoiceLanguageModeId()
return mVoiceLanguageModeId;
|
public int | getVoiceMaxResults()
return mVoiceMaxResults;
|
public int | getVoicePromptTextId()
return mVoicePromptTextId;
|
public boolean | getVoiceSearchEnabled()
return 0 != (mVoiceSearchMode & VOICE_SEARCH_SHOW_BUTTON);
|
public boolean | getVoiceSearchLaunchRecognizer()
return 0 != (mVoiceSearchMode & VOICE_SEARCH_LAUNCH_RECOGNIZER);
|
public boolean | getVoiceSearchLaunchWebSearch()
return 0 != (mVoiceSearchMode & VOICE_SEARCH_LAUNCH_WEB_SEARCH);
|
public static void | setDefaultSearchable(android.content.Context context, android.content.ComponentName activity)Set the default searchable activity (when none is specified). // voiceMaxResults
synchronized (SearchableInfo.class) {
SearchableInfo si = null;
if (activity != null) {
si = getSearchableInfo(context, activity);
if (si != null) {
// move to front of list
sSearchablesList.remove(si);
sSearchablesList.add(0, si);
}
}
sDefaultSearchable = si;
}
|
private void | setSearchModeFlags()Convert searchmode to flags.
mBadgeLabel = (0 != (mSearchMode & 4));
mBadgeIcon = (0 != (mSearchMode & 8)) && (mIconId != 0);
mQueryRewriteFromData = (0 != (mSearchMode & 0x10));
mQueryRewriteFromText = (0 != (mSearchMode & 0x20));
|
public void | writeToParcel(android.os.Parcel dest, int flags)
dest.writeInt(mSearchable ? 1 : 0);
dest.writeInt(mLabelId);
mSearchActivity.writeToParcel(dest, flags);
dest.writeInt(mHintId);
dest.writeInt(mSearchMode);
dest.writeInt(mIconId);
dest.writeInt(mSearchButtonText);
dest.writeInt(mSearchInputType);
dest.writeInt(mSearchImeOptions);
dest.writeString(mSuggestAuthority);
dest.writeString(mSuggestPath);
dest.writeString(mSuggestSelection);
dest.writeString(mSuggestIntentAction);
dest.writeString(mSuggestIntentData);
// This is usually a very short linked list so we'll just pre-count it
ActionKeyInfo nextKeyInfo = mActionKeyList;
int count = 0;
while (nextKeyInfo != null) {
++count;
nextKeyInfo = nextKeyInfo.mNext;
}
dest.writeInt(count);
// Now write count of 'em
nextKeyInfo = mActionKeyList;
while (count-- > 0) {
nextKeyInfo.writeToParcel(dest, flags);
}
dest.writeString(mSuggestProviderPackage);
dest.writeInt(mVoiceSearchMode);
dest.writeInt(mVoiceLanguageModeId);
dest.writeInt(mVoicePromptTextId);
dest.writeInt(mVoiceLanguageId);
dest.writeInt(mVoiceMaxResults);
|