summaryrefslogtreecommitdiffstats
path: root/core/java/android/server/search
diff options
context:
space:
mode:
authorKarl Rosaen <>2009-04-23 19:00:21 -0700
committerThe Android Open Source Project <initial-contribution@android.com>2009-04-23 19:00:21 -0700
commit875d50a4b9294b2be33cff6493cae7acd1d07ea7 (patch)
tree48cc044c4719e53d214e5fa6c273d1ecd9078356 /core/java/android/server/search
parentb08971b876801d9cb878f3f0ca0ebfde7c9bea8e (diff)
downloadframeworks_base-875d50a4b9294b2be33cff6493cae7acd1d07ea7.zip
frameworks_base-875d50a4b9294b2be33cff6493cae7acd1d07ea7.tar.gz
frameworks_base-875d50a4b9294b2be33cff6493cae7acd1d07ea7.tar.bz2
AI 147564: Merge back from search branch to donut. Notes:
- all public apis and framework changes have been reviewed by relevant folks in our branch (e.g romainguy) - all new public apis are @hidden; they will still get reviewed by api council once we're in git - other than that, it's mostly GlobalSearch and search dialog stuff, a new apps provider, and some tweaks to the contacts provider that was reviewed by jham Automated import of CL 147564
Diffstat (limited to 'core/java/android/server/search')
-rw-r--r--core/java/android/server/search/SearchManagerService.java49
-rw-r--r--core/java/android/server/search/SearchableInfo.java362
-rw-r--r--core/java/android/server/search/Searchables.java243
3 files changed, 351 insertions, 303 deletions
diff --git a/core/java/android/server/search/SearchManagerService.java b/core/java/android/server/search/SearchManagerService.java
index fe15553..eaace6b 100644
--- a/core/java/android/server/search/SearchManagerService.java
+++ b/core/java/android/server/search/SearchManagerService.java
@@ -22,8 +22,8 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Handler;
-import android.util.Config;
/**
* This is a simplified version of the Search Manager service. It no longer handles
@@ -36,7 +36,6 @@ public class SearchManagerService extends ISearchManager.Stub
// general debugging support
private static final String TAG = "SearchManagerService";
private static final boolean DEBUG = false;
- private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
// configuration choices
private static final boolean IMMEDIATE_SEARCHABLES_UPDATE = true;
@@ -45,9 +44,10 @@ public class SearchManagerService extends ISearchManager.Stub
private final Context mContext;
private final Handler mHandler;
private boolean mSearchablesDirty;
+ private Searchables mSearchables;
/**
- * Initialize the Search Manager service in the provided system context.
+ * Initializes the Search Manager service in the provided system context.
* Only one instance of this object should be created!
*
* @param context to use for accessing DB, window manager, etc.
@@ -55,6 +55,8 @@ public class SearchManagerService extends ISearchManager.Stub
public SearchManagerService(Context context) {
mContext = context;
mHandler = new Handler();
+ mSearchablesDirty = true;
+ mSearchables = new Searchables(context);
// Setup the infrastructure for updating and maintaining the list
// of searchable activities.
@@ -64,7 +66,6 @@ public class SearchManagerService extends ISearchManager.Stub
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addDataScheme("package");
mContext.registerReceiver(mIntentReceiver, filter, null, mHandler);
- mSearchablesDirty = true;
// After startup settles down, preload the searchables list,
// which will reduce the delay when the search UI is invoked.
@@ -109,34 +110,41 @@ public class SearchManagerService extends ISearchManager.Stub
};
/**
- * Update the list of searchables, either at startup or in response to
+ * Updates the list of searchables, either at startup or in response to
* a package add/remove broadcast message.
*/
private void updateSearchables() {
- SearchableInfo.buildSearchableList(mContext);
+ mSearchables.buildSearchableList();
mSearchablesDirty = false;
- // TODO This is a hack. This shouldn't be hardcoded here, it's probably
- // a policy.
-// ComponentName defaultSearch = new ComponentName(
-// "com.android.contacts",
-// "com.android.contacts.ContactsListActivity" );
- ComponentName defaultSearch = new ComponentName(
- "com.android.googlesearch",
- "com.android.googlesearch.GoogleSearch" );
- SearchableInfo.setDefaultSearchable(mContext, defaultSearch);
+ // TODO SearchableInfo should be the source of truth about whether a searchable exists.
+ // As it stands, if the package exists but is misconfigured in some way, then this
+ // would fail, and needs to be fixed.
+ ComponentName defaultSearch = new ComponentName(
+ "com.android.globalsearch",
+ "com.android.globalsearch.GlobalSearch");
+
+ try {
+ mContext.getPackageManager().getActivityInfo(defaultSearch, 0);
+ } catch (NameNotFoundException e) {
+ defaultSearch = new ComponentName(
+ "com.android.googlesearch",
+ "com.android.googlesearch.GoogleSearch");
+ }
+
+ mSearchables.setDefaultSearchable(defaultSearch);
}
/**
- * Return the searchableinfo for a given activity
+ * Returns the SearchableInfo for a given activity
*
* @param launchActivity The activity from which we're launching this search.
- * @return Returns a SearchableInfo record describing the parameters of the search,
- * or null if no searchable metadata was available.
* @param globalSearch If false, this will only launch the search that has been specifically
* defined by the application (which is usually defined as a local search). If no default
* search is defined in the current application or activity, no search will be launched.
* If true, this will always launch a platform-global (e.g. web-based) search instead.
+ * @return Returns a SearchableInfo record describing the parameters of the search,
+ * or null if no searchable metadata was available.
*/
public SearchableInfo getSearchableInfo(ComponentName launchActivity, boolean globalSearch) {
// final check. however we should try to avoid this, because
@@ -146,11 +154,12 @@ public class SearchManagerService extends ISearchManager.Stub
}
SearchableInfo si = null;
if (globalSearch) {
- si = SearchableInfo.getDefaultSearchable();
+ si = mSearchables.getDefaultSearchable();
} else {
- si = SearchableInfo.getSearchableInfo(mContext, launchActivity);
+ si = mSearchables.getSearchableInfo(launchActivity);
}
return si;
}
+
}
diff --git a/core/java/android/server/search/SearchableInfo.java b/core/java/android/server/search/SearchableInfo.java
index 0c04839..22abd1b 100644
--- a/core/java/android/server/search/SearchableInfo.java
+++ b/core/java/android/server/search/SearchableInfo.java
@@ -21,14 +21,11 @@ import org.xmlpull.v1.XmlPullParserException;
import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
-import android.content.pm.ResolveInfo;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
-import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.InputType;
@@ -38,9 +35,6 @@ import android.util.Xml;
import android.view.inputmethod.EditorInfo;
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
public final class SearchableInfo implements Parcelable {
@@ -50,19 +44,12 @@ public final class SearchableInfo implements Parcelable {
// set this flag to 1 to prevent any apps from providing suggestions
final static int DBG_INHIBIT_SUGGESTIONS = 0;
- // static strings used for XML lookups, etc.
+ // static strings used for XML lookups.
// TODO how should these be documented for the developer, in a more structured way than
// the current long wordy javadoc in SearchManager.java ?
- private static final String MD_LABEL_DEFAULT_SEARCHABLE = "android.app.default_searchable";
private static final String MD_LABEL_SEARCHABLE = "android.app.searchable";
- private static final String MD_SEARCHABLE_SYSTEM_SEARCH = "*";
private static final String MD_XML_ELEMENT_SEARCHABLE = "searchable";
private static final String MD_XML_ELEMENT_SEARCHABLE_ACTION_KEY = "actionkey";
-
- // class maintenance and general shared data
- private static HashMap<ComponentName, SearchableInfo> sSearchablesMap = null;
- private static ArrayList<SearchableInfo> sSearchablesList = null;
- private static SearchableInfo sDefaultSearchable = null;
// true member variables - what we know about the searchability
// TO-DO replace public with getters
@@ -86,7 +73,6 @@ public final class SearchableInfo implements Parcelable {
private String mSuggestIntentData = null;
private ActionKeyInfo mActionKeyList = null;
private String mSuggestProviderPackage = null;
- private Context mCacheActivityContext = null; // use during setup only - don't hold memory!
// Flag values for Searchable_voiceSearchMode
private static int VOICE_SEARCH_SHOW_BUTTON = 1;
@@ -97,37 +83,7 @@ public final class SearchableInfo implements Parcelable {
private int mVoicePromptTextId; // voicePromptText
private int mVoiceLanguageId; // voiceLanguage
private int mVoiceMaxResults; // voiceMaxResults
-
- /**
- * Set the default searchable activity (when none is specified).
- */
- public static void setDefaultSearchable(Context context,
- ComponentName activity) {
- 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;
- }
- }
-
- /**
- * Provides the system-default search activity, which you can use
- * whenever getSearchableInfo() returns null;
- *
- * @return Returns the system-default search activity, null if never defined
- */
- public static SearchableInfo getDefaultSearchable() {
- synchronized (SearchableInfo.class) {
- return sDefaultSearchable;
- }
- }
+
/**
* Retrieve the authority for obtaining search suggestions.
@@ -193,9 +149,16 @@ public final class SearchableInfo implements Parcelable {
* @return Returns a context related to the searchable activity
*/
public Context getActivityContext(Context context) {
+ return createActivityContext(context, mSearchActivity);
+ }
+
+ /**
+ * Creates a context for another activity.
+ */
+ private static Context createActivityContext(Context context, ComponentName activity) {
Context theirContext = null;
try {
- theirContext = context.createPackageContext(mSearchActivity.getPackageName(), 0);
+ theirContext = context.createPackageContext(activity.getPackageName(), 0);
} catch (PackageManager.NameNotFoundException e) {
// unexpected, but we deal with this by null-checking theirContext
} catch (java.lang.SecurityException e) {
@@ -234,242 +197,68 @@ public final class SearchableInfo implements Parcelable {
}
/**
- * Factory. Look up, or construct, based on the activity.
- *
- * The activities fall into three cases, based on meta-data found in
- * the manifest entry:
- * <ol>
- * <li>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.</li>
- * <li>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.</li>
- * <li>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.
- *
- * @param activity The name of the current activity, or null if the
- * activity does not define any explicit searchable metadata.
- */
- public static SearchableInfo getSearchableInfo(Context context,
- ComponentName activity) {
- // 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;
-
- }
-
- /**
- * 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.
- *
- * @param context a context we can use during this work
- */
- public static void buildSearchableList(Context context) {
-
- // 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;
- }
- }
-
- /**
* Constructor
*
* Given a ComponentName, get the searchability info
* and build a local copy of it. Use the factory, not this.
*
- * @param context runtime context
+ * @param activityContext runtime context for the activity that the searchable info is about.
* @param attr The attribute set we found in the XML file, contains the values that are used to
* construct the object.
* @param cName The component name of the searchable activity
*/
- private SearchableInfo(Context context, AttributeSet attr, final ComponentName cName) {
+ private SearchableInfo(Context activityContext, AttributeSet attr, final ComponentName cName) {
// initialize as an "unsearchable" object
mSearchable = false;
mSearchActivity = cName;
- // to access another activity's resources, I need its context.
- // BE SURE to release the cache sometime after construction - it's a large object to hold
- mCacheActivityContext = getActivityContext(context);
- if (mCacheActivityContext != null) {
- TypedArray a = mCacheActivityContext.obtainStyledAttributes(attr,
- com.android.internal.R.styleable.Searchable);
- mSearchMode = a.getInt(com.android.internal.R.styleable.Searchable_searchMode, 0);
- mLabelId = a.getResourceId(com.android.internal.R.styleable.Searchable_label, 0);
- mHintId = a.getResourceId(com.android.internal.R.styleable.Searchable_hint, 0);
- mIconId = a.getResourceId(com.android.internal.R.styleable.Searchable_icon, 0);
- mSearchButtonText = a.getResourceId(
- com.android.internal.R.styleable.Searchable_searchButtonText, 0);
- mSearchInputType = a.getInt(com.android.internal.R.styleable.Searchable_inputType,
- InputType.TYPE_CLASS_TEXT |
- InputType.TYPE_TEXT_VARIATION_NORMAL);
- mSearchImeOptions = a.getInt(com.android.internal.R.styleable.Searchable_imeOptions,
- EditorInfo.IME_ACTION_SEARCH);
+ TypedArray a = activityContext.obtainStyledAttributes(attr,
+ com.android.internal.R.styleable.Searchable);
+ mSearchMode = a.getInt(com.android.internal.R.styleable.Searchable_searchMode, 0);
+ mLabelId = a.getResourceId(com.android.internal.R.styleable.Searchable_label, 0);
+ mHintId = a.getResourceId(com.android.internal.R.styleable.Searchable_hint, 0);
+ mIconId = a.getResourceId(com.android.internal.R.styleable.Searchable_icon, 0);
+ mSearchButtonText = a.getResourceId(
+ com.android.internal.R.styleable.Searchable_searchButtonText, 0);
+ mSearchInputType = a.getInt(com.android.internal.R.styleable.Searchable_inputType,
+ InputType.TYPE_CLASS_TEXT |
+ InputType.TYPE_TEXT_VARIATION_NORMAL);
+ mSearchImeOptions = a.getInt(com.android.internal.R.styleable.Searchable_imeOptions,
+ EditorInfo.IME_ACTION_SEARCH);
- setSearchModeFlags();
- if (DBG_INHIBIT_SUGGESTIONS == 0) {
- mSuggestAuthority = a.getString(
- com.android.internal.R.styleable.Searchable_searchSuggestAuthority);
- mSuggestPath = a.getString(
- com.android.internal.R.styleable.Searchable_searchSuggestPath);
- mSuggestSelection = a.getString(
- com.android.internal.R.styleable.Searchable_searchSuggestSelection);
- mSuggestIntentAction = a.getString(
- com.android.internal.R.styleable.Searchable_searchSuggestIntentAction);
- mSuggestIntentData = a.getString(
- com.android.internal.R.styleable.Searchable_searchSuggestIntentData);
- }
- mVoiceSearchMode =
- a.getInt(com.android.internal.R.styleable.Searchable_voiceSearchMode, 0);
- // TODO this didn't work - came back zero from YouTube
- mVoiceLanguageModeId =
- a.getResourceId(com.android.internal.R.styleable.Searchable_voiceLanguageModel, 0);
- mVoicePromptTextId =
- a.getResourceId(com.android.internal.R.styleable.Searchable_voicePromptText, 0);
- mVoiceLanguageId =
- a.getResourceId(com.android.internal.R.styleable.Searchable_voiceLanguage, 0);
- mVoiceMaxResults =
- a.getInt(com.android.internal.R.styleable.Searchable_voiceMaxResults, 0);
+ setSearchModeFlags();
+ if (DBG_INHIBIT_SUGGESTIONS == 0) {
+ mSuggestAuthority = a.getString(
+ com.android.internal.R.styleable.Searchable_searchSuggestAuthority);
+ mSuggestPath = a.getString(
+ com.android.internal.R.styleable.Searchable_searchSuggestPath);
+ mSuggestSelection = a.getString(
+ com.android.internal.R.styleable.Searchable_searchSuggestSelection);
+ mSuggestIntentAction = a.getString(
+ com.android.internal.R.styleable.Searchable_searchSuggestIntentAction);
+ mSuggestIntentData = a.getString(
+ com.android.internal.R.styleable.Searchable_searchSuggestIntentData);
+ }
+ mVoiceSearchMode =
+ a.getInt(com.android.internal.R.styleable.Searchable_voiceSearchMode, 0);
+ // TODO this didn't work - came back zero from YouTube
+ mVoiceLanguageModeId =
+ a.getResourceId(com.android.internal.R.styleable.Searchable_voiceLanguageModel, 0);
+ mVoicePromptTextId =
+ a.getResourceId(com.android.internal.R.styleable.Searchable_voicePromptText, 0);
+ mVoiceLanguageId =
+ a.getResourceId(com.android.internal.R.styleable.Searchable_voiceLanguage, 0);
+ mVoiceMaxResults =
+ a.getInt(com.android.internal.R.styleable.Searchable_voiceMaxResults, 0);
- a.recycle();
+ a.recycle();
- // get package info for suggestions provider (if any)
- if (mSuggestAuthority != null) {
- ProviderInfo pi =
- context.getPackageManager().resolveContentProvider(mSuggestAuthority,
- 0);
- if (pi != null) {
- mSuggestProviderPackage = pi.packageName;
- }
+ // get package info for suggestions provider (if any)
+ if (mSuggestAuthority != null) {
+ PackageManager pm = activityContext.getPackageManager();
+ ProviderInfo pi = pm.resolveContentProvider(mSuggestAuthority, 0);
+ if (pi != null) {
+ mSuggestProviderPackage = pi.packageName;
}
}
@@ -496,7 +285,7 @@ public final class SearchableInfo implements Parcelable {
/**
* Private class used to hold the "action key" configuration
*/
- public class ActionKeyInfo implements Parcelable {
+ public static class ActionKeyInfo implements Parcelable {
public int mKeyCode = 0;
public String mQueryActionMsg;
@@ -506,14 +295,15 @@ public final class SearchableInfo implements Parcelable {
/**
* Create one object using attributeset as input data.
- * @param context runtime context
+ * @param activityContext runtime context of the activity that the action key information
+ * is about.
* @param attr The attribute set we found in the XML file, contains the values that are used to
* construct the object.
* @param next We'll build these up using a simple linked list (since there are usually
* just zero or one).
*/
- public ActionKeyInfo(Context context, AttributeSet attr, ActionKeyInfo next) {
- TypedArray a = mCacheActivityContext.obtainStyledAttributes(attr,
+ public ActionKeyInfo(Context activityContext, AttributeSet attr, ActionKeyInfo next) {
+ TypedArray a = activityContext.obtainStyledAttributes(attr,
com.android.internal.R.styleable.SearchableActionKey);
mKeyCode = a.getInt(
@@ -584,6 +374,20 @@ public final class SearchableInfo implements Parcelable {
return null;
}
+ public static SearchableInfo getActivityMetaData(Context context, ActivityInfo activityInfo) {
+ // for each component, try to find metadata
+ XmlResourceParser xml =
+ activityInfo.loadXmlMetaData(context.getPackageManager(), MD_LABEL_SEARCHABLE);
+ if (xml == null) {
+ return null;
+ }
+ ComponentName cName = new ComponentName(activityInfo.packageName, activityInfo.name);
+
+ SearchableInfo searchable = getActivityMetaData(context, xml, cName);
+ xml.close();
+ return searchable;
+ }
+
/**
* Get the metadata for a given activity
*
@@ -598,6 +402,7 @@ public final class SearchableInfo implements Parcelable {
private static SearchableInfo getActivityMetaData(Context context, XmlPullParser xml,
final ComponentName cName) {
SearchableInfo result = null;
+ Context activityContext = createActivityContext(context, cName);
// 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.
@@ -608,7 +413,7 @@ public final class SearchableInfo implements Parcelable {
if (xml.getName().equals(MD_XML_ELEMENT_SEARCHABLE)) {
AttributeSet attr = Xml.asAttributeSet(xml);
if (attr != null) {
- result = new SearchableInfo(context, attr, cName);
+ result = new SearchableInfo(activityContext, attr, cName);
// if the constructor returned a bad object, exit now.
if (! result.mSearchable) {
return null;
@@ -621,7 +426,7 @@ public final class SearchableInfo implements Parcelable {
}
AttributeSet attr = Xml.asAttributeSet(xml);
if (attr != null) {
- ActionKeyInfo keyInfo = result.new ActionKeyInfo(context, attr,
+ ActionKeyInfo keyInfo = new ActionKeyInfo(activityContext, attr,
result.mActionKeyList);
// only add to list if it is was useable
if (keyInfo.mKeyCode != 0) {
@@ -637,6 +442,7 @@ public final class SearchableInfo implements Parcelable {
} catch (IOException e) {
throw new RuntimeException(e);
}
+
return result;
}
@@ -757,16 +563,6 @@ public final class SearchableInfo implements Parcelable {
}
/**
- * Return the list of searchable activities, for use in the drop-down.
- */
- public static ArrayList<SearchableInfo> getSearchablesList() {
- synchronized (SearchableInfo.class) {
- ArrayList<SearchableInfo> result = new ArrayList<SearchableInfo>(sSearchablesList);
- return result;
- }
- }
-
- /**
* Support for parcelable and aidl operations.
*/
public static final Parcelable.Creator<SearchableInfo> CREATOR
diff --git a/core/java/android/server/search/Searchables.java b/core/java/android/server/search/Searchables.java
new file mode 100644
index 0000000..ba75d21
--- /dev/null
+++ b/core/java/android/server/search/Searchables.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.search;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * This class maintains the information about all searchable activities.
+ */
+public class Searchables {
+
+ // static strings used for XML lookups, etc.
+ // TODO how should these be documented for the developer, in a more structured way than
+ // the current long wordy javadoc in SearchManager.java ?
+ private static final String MD_LABEL_DEFAULT_SEARCHABLE = "android.app.default_searchable";
+ private static final String MD_SEARCHABLE_SYSTEM_SEARCH = "*";
+
+ private Context mContext;
+
+ private HashMap<ComponentName, SearchableInfo> mSearchablesMap = null;
+ private ArrayList<SearchableInfo> mSearchablesList = null;
+ private SearchableInfo mDefaultSearchable = null;
+
+ /**
+ *
+ * @param context Context to use for looking up activities etc.
+ */
+ public Searchables (Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Look up, or construct, based on the activity.
+ *
+ * The activities fall into three cases, based on meta-data found in
+ * the manifest entry:
+ * <ol>
+ * <li>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.</li>
+ * <li>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.</li>
+ * <li>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.
+ *
+ * @param activity The name of the current activity, or null if the
+ * activity does not define any explicit searchable metadata.
+ */
+ public SearchableInfo getSearchableInfo(ComponentName activity) {
+ // Step 1. Is the result already hashed? (case 1)
+ SearchableInfo result;
+ synchronized (this) {
+ result = mSearchablesMap.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;
+ try {
+ ai = mContext.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 (this) {
+ result = mSearchablesMap.get(referredActivity);
+ if (result != null) {
+ mSearchablesMap.put(activity, result);
+ return result;
+ }
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // case 3: no metadata
+ }
+
+ // Step 3. None found. Return null.
+ return null;
+
+ }
+
+ /**
+ * Set the default searchable activity (when none is specified).
+ */
+ public synchronized void setDefaultSearchable(ComponentName activity) {
+ SearchableInfo si = null;
+ if (activity != null) {
+ si = getSearchableInfo(activity);
+ if (si != null) {
+ // move to front of list
+ mSearchablesList.remove(si);
+ mSearchablesList.add(0, si);
+ }
+ }
+ mDefaultSearchable = si;
+ }
+
+ /**
+ * Provides the system-default search activity, which you can use
+ * whenever getSearchableInfo() returns null;
+ *
+ * @return Returns the system-default search activity, null if never defined
+ */
+ public synchronized SearchableInfo getDefaultSearchable() {
+ return mDefaultSearchable;
+ }
+
+ public synchronized boolean isDefaultSearchable(SearchableInfo searchable) {
+ return searchable == mDefaultSearchable;
+ }
+
+ /**
+ * 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.
+ */
+ public void buildSearchableList() {
+
+ // 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 = mContext.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;
+ SearchableInfo searchable = SearchableInfo.getActivityMetaData(mContext, ai);
+ if (searchable != null) {
+ newSearchablesList.add(searchable);
+ newSearchablesMap.put(searchable.mSearchActivity, searchable);
+ }
+ }
+ }
+
+ // record the final values as a coherent pair
+ synchronized (this) {
+ mSearchablesList = newSearchablesList;
+ mSearchablesMap = newSearchablesMap;
+ }
+ }
+
+ /**
+ * Returns the list of searchable activities.
+ */
+ public synchronized ArrayList<SearchableInfo> getSearchablesList() {
+ ArrayList<SearchableInfo> result = new ArrayList<SearchableInfo>(mSearchablesList);
+ return result;
+ }
+}