diff options
author | Karl Rosaen <> | 2009-04-23 19:00:21 -0700 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-04-23 19:00:21 -0700 |
commit | 875d50a4b9294b2be33cff6493cae7acd1d07ea7 (patch) | |
tree | 48cc044c4719e53d214e5fa6c273d1ecd9078356 /core/java/android/server/search | |
parent | b08971b876801d9cb878f3f0ca0ebfde7c9bea8e (diff) | |
download | frameworks_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.java | 49 | ||||
-rw-r--r-- | core/java/android/server/search/SearchableInfo.java | 362 | ||||
-rw-r--r-- | core/java/android/server/search/Searchables.java | 243 |
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; + } +} |