diff options
author | Satish Sampath <satish@android.com> | 2009-06-04 11:51:17 +0100 |
---|---|---|
committer | Satish Sampath <satish@android.com> | 2009-06-05 10:34:53 +0100 |
commit | f9acde27486bcc6eea1092073f7b47c31749efd6 (patch) | |
tree | f82fb2fb4417e0774c8bfc56e21344877238dce7 /core | |
parent | 7ebda6f110708bdb704f8700a6b3473249b12baf (diff) | |
download | frameworks_base-f9acde27486bcc6eea1092073f7b47c31749efd6.zip frameworks_base-f9acde27486bcc6eea1092073f7b47c31749efd6.tar.gz frameworks_base-f9acde27486bcc6eea1092073f7b47c31749efd6.tar.bz2 |
Include web search providers in Searchables.
- Along with ACTION_SEARCH we now enumate ACTION_WEB_SEARCH as well so web search providers are covered in the searchables list. This fixes a broken unit test.
- Moved get/setPreferredWebSearchActivity and get-all-web-search-providers implementation to this module when the searchables list gets updated, so that it happens on boot and on package add/remove events and remains up to date. The duplicate code in WebSearchProvider will be removed in a separate change.
- Also made Searchables broadcast an intent when the searchables list got rebuilt, so components such as GlobalSearch/SuggestionSources no longer need to do this on their own.
Diffstat (limited to 'core')
-rw-r--r-- | core/java/android/app/ISearchManager.aidl | 3 | ||||
-rw-r--r-- | core/java/android/app/SearchManager.java | 54 | ||||
-rw-r--r-- | core/java/android/server/search/SearchManagerService.java | 47 | ||||
-rw-r--r-- | core/java/android/server/search/Searchables.java | 228 | ||||
-rw-r--r-- | core/res/res/values/arrays.xml | 10 |
5 files changed, 281 insertions, 61 deletions
diff --git a/core/java/android/app/ISearchManager.aidl b/core/java/android/app/ISearchManager.aidl index 39eb4f1..374423e 100644 --- a/core/java/android/app/ISearchManager.aidl +++ b/core/java/android/app/ISearchManager.aidl @@ -23,4 +23,7 @@ import android.server.search.SearchableInfo; interface ISearchManager { SearchableInfo getSearchableInfo(in ComponentName launchActivity, boolean globalSearch); List<SearchableInfo> getSearchablesInGlobalSearch(); + List<SearchableInfo> getSearchablesForWebSearch(); + SearchableInfo getDefaultSearchableForWebSearch(); + void setDefaultWebSearch(in ComponentName component); } diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java index b0c248c..f1cc24a 100644 --- a/core/java/android/app/SearchManager.java +++ b/core/java/android/app/SearchManager.java @@ -1417,6 +1417,16 @@ public class SearchManager = "android.search.action.WEB_SEARCH_SETTINGS"; /** + * Intent action broadcasted to inform that the searchables list or default have changed. + * Components should handle this intent if they cache any searchable data and wish to stay + * up to date on changes. + * + * @hide Pending API council approval. + */ + public final static String INTENT_ACTION_SEARCHABLES_CHANGED + = "android.search.action.SEARCHABLES_CHANGED"; + + /** * If a suggestion has this value in {@link #SUGGEST_COLUMN_INTENT_ACTION}, * the search dialog will take no action. * @@ -1744,4 +1754,48 @@ public class SearchManager return null; } } + + /** + * Returns a list of the searchable activities that handle web searches. + * + * @return a a list of all searchable activities that handle {@link SearchManager#ACTION_WEB_SEARCH}. + * + * @hide because SearchableInfo is not part of the API. + */ + public static List<SearchableInfo> getSearchablesForWebSearch() { + try { + return sService.getSearchablesForWebSearch(); + } catch (RemoteException e) { + return null; + } + } + + /** + * Returns the default searchable activity for web searches. + * + * @return searchable information for the activity handling web searches by default. + * + * @hide because SearchableInfo is not part of the API. + */ + public static SearchableInfo getDefaultSearchableForWebSearch() { + try { + return sService.getDefaultSearchableForWebSearch(); + } catch (RemoteException e) { + return null; + } + } + + /** + * Sets the default searchable activity for web searches. + * + * @param component Name of the component to set as default activity for web searches. + * + * @hide + */ + public static void setDefaultWebSearch(ComponentName component) { + try { + sService.setDefaultWebSearch(component); + } catch (RemoteException e) { + } + } } diff --git a/core/java/android/server/search/SearchManagerService.java b/core/java/android/server/search/SearchManagerService.java index 952372f..060bcea 100644 --- a/core/java/android/server/search/SearchManagerService.java +++ b/core/java/android/server/search/SearchManagerService.java @@ -23,12 +23,13 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Handler; +import android.os.RemoteException; import java.util.List; /** * This is a simplified version of the Search Manager service. It no longer handles - * presentation (UI). Its function is to maintain the map & list of "searchable" + * presentation (UI). Its function is to maintain the map & list of "searchable" * items, which provides a mapping from individual activities (where a user might have * invoked search) to specific searchable activities (where the search will be dispatched). */ @@ -43,19 +44,19 @@ public class SearchManagerService extends ISearchManager.Stub private final Handler mHandler; private boolean mSearchablesDirty; private Searchables mSearchables; - + /** * 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. */ - public SearchManagerService(Context context) { + 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. IntentFilter filter = new IntentFilter(); @@ -64,15 +65,15 @@ public class SearchManagerService extends ISearchManager.Stub filter.addAction(Intent.ACTION_PACKAGE_CHANGED); filter.addDataScheme("package"); mContext.registerReceiver(mIntentReceiver, filter, null, mHandler); - + // After startup settles down, preload the searchables list, // which will reduce the delay when the search UI is invoked. mHandler.post(mRunUpdateSearchable); } - + /** * Listens for intent broadcasts. - * + * * The primary purpose here is to refresh the "searchables" list * if packages are added/removed. */ @@ -80,7 +81,7 @@ public class SearchManagerService extends ISearchManager.Stub @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - + // First, test for intents that matter at any time if (action.equals(Intent.ACTION_PACKAGE_ADDED) || action.equals(Intent.ACTION_PACKAGE_REMOVED) || @@ -91,14 +92,14 @@ public class SearchManagerService extends ISearchManager.Stub } } }; - + /** * This runnable (for the main handler / UI thread) will update the searchables list. */ private Runnable mRunUpdateSearchable = new Runnable() { public void run() { updateSearchablesIfDirty(); - } + } }; /** @@ -124,7 +125,7 @@ public class SearchManagerService extends ISearchManager.Stub * * @param launchActivity The activity from which we're launching this search. * @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 + * 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, @@ -141,7 +142,7 @@ public class SearchManagerService extends ISearchManager.Stub return si; } - + /** * Returns a list of the searchable activities that can be included in global search. */ @@ -150,4 +151,26 @@ public class SearchManagerService extends ISearchManager.Stub return mSearchables.getSearchablesInGlobalSearchList(); } + /** + * Returns a list of the searchable activities that handle web searches. + */ + public List<SearchableInfo> getSearchablesForWebSearch() { + updateSearchablesIfDirty(); + return mSearchables.getSearchablesForWebSearchList(); + } + + /** + * Returns the default searchable activity for web searches. + */ + public SearchableInfo getDefaultSearchableForWebSearch() { + updateSearchablesIfDirty(); + return mSearchables.getDefaultSearchableForWebSearch(); + } + + /** + * Sets the default searchable activity for web searches. + */ + public void setDefaultWebSearch(ComponentName component) { + mSearchables.setDefaultWebSearch(component); + } } diff --git a/core/java/android/server/search/Searchables.java b/core/java/android/server/search/Searchables.java index 62631d6..a27667b 100644 --- a/core/java/android/server/search/Searchables.java +++ b/core/java/android/server/search/Searchables.java @@ -16,13 +16,18 @@ package android.server.search; +import com.android.internal.app.ResolverActivity; +import com.android.internal.R; + import android.app.SearchManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.res.Resources; import android.os.Bundle; import android.util.Log; @@ -31,37 +36,39 @@ import java.util.HashMap; import java.util.List; /** - * This class maintains the information about all searchable activities. + * This class maintains the information about all searchable activities. */ public class Searchables { private static final String LOG_TAG = "Searchables"; // static strings used for XML lookups, etc. - // TODO how should these be documented for the developer, in a more structured way than + // 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 ArrayList<SearchableInfo> mSearchablesInGlobalSearchList = null; + private ArrayList<SearchableInfo> mSearchablesForWebSearchList = null; private SearchableInfo mDefaultSearchable = null; - + private SearchableInfo mDefaultSearchableForWebSearch = 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 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 @@ -73,16 +80,16 @@ public class Searchables { * 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 + * 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 + * @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) { @@ -92,18 +99,18 @@ public class Searchables { 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. + // 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) { @@ -116,11 +123,11 @@ public class Searchables { 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 + // 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(); @@ -146,80 +153,101 @@ public class Searchables { } catch (PackageManager.NameNotFoundException e) { // case 3: no metadata } - + // Step 3. None found. Return null. return null; - + } - + /** * 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. - * + * Builds an entire list (suitable for display) of + * activities that are searchable, by iterating the entire set of + * ACTION_SEARCH & ACTION_WEB_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() { // These will become the new values at the end of the method - HashMap<ComponentName, SearchableInfo> newSearchablesMap + HashMap<ComponentName, SearchableInfo> newSearchablesMap = new HashMap<ComponentName, SearchableInfo>(); ArrayList<SearchableInfo> newSearchablesList = new ArrayList<SearchableInfo>(); ArrayList<SearchableInfo> newSearchablesInGlobalSearchList = new ArrayList<SearchableInfo>(); + ArrayList<SearchableInfo> newSearchablesForWebSearchList + = new ArrayList<SearchableInfo>(); final PackageManager pm = mContext.getPackageManager(); - - // use intent resolver to generate list of ACTION_SEARCH receivers - List<ResolveInfo> infoList; + + // Use intent resolver to generate list of ACTION_SEARCH & ACTION_WEB_SEARCH receivers. + List<ResolveInfo> searchList; final Intent intent = new Intent(Intent.ACTION_SEARCH); - infoList = pm.queryIntentActivities(intent, PackageManager.GET_META_DATA); - + searchList = pm.queryIntentActivities(intent, PackageManager.GET_META_DATA); + + List<ResolveInfo> webSearchInfoList; + final Intent webSearchIntent = new Intent(Intent.ACTION_WEB_SEARCH); + webSearchInfoList = pm.queryIntentActivities(webSearchIntent, PackageManager.GET_META_DATA); + // analyze each one, generate a Searchables record, and record - if (infoList != null) { - int count = infoList.size(); + if (searchList != null || webSearchInfoList != null) { + int search_count = (searchList == null ? 0 : searchList.size()); + int web_search_count = (webSearchInfoList == null ? 0 : webSearchInfoList.size()); + int count = search_count + web_search_count; for (int ii = 0; ii < count; ii++) { // for each component, try to find metadata - ResolveInfo info = infoList.get(ii); + ResolveInfo info = (ii < search_count) + ? searchList.get(ii) + : webSearchInfoList.get(ii - search_count); ActivityInfo ai = info.activityInfo; - SearchableInfo searchable = SearchableInfo.getActivityMetaData(mContext, ai); - if (searchable != null) { - newSearchablesList.add(searchable); - newSearchablesMap.put(searchable.getSearchActivity(), searchable); - if (searchable.shouldIncludeInGlobalSearch()) { - newSearchablesInGlobalSearchList.add(searchable); + // Check first to avoid duplicate entries. + if (newSearchablesMap.get(new ComponentName(ai.packageName, ai.name)) == null) { + SearchableInfo searchable = SearchableInfo.getActivityMetaData(mContext, ai); + if (searchable != null) { + newSearchablesList.add(searchable); + newSearchablesMap.put(searchable.getSearchActivity(), searchable); + if (searchable.shouldIncludeInGlobalSearch()) { + newSearchablesInGlobalSearchList.add(searchable); + } } } } } - + + if (webSearchInfoList != null) { + for (int i = 0; i < webSearchInfoList.size(); ++i) { + ActivityInfo ai = webSearchInfoList.get(i).activityInfo; + ComponentName component = new ComponentName(ai.packageName, ai.name); + newSearchablesForWebSearchList.add(newSearchablesMap.get(component)); + } + } + // Find the global search provider Intent globalSearchIntent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH); ComponentName globalSearchActivity = globalSearchIntent.resolveActivity(pm); @@ -230,15 +258,95 @@ public class Searchables { + globalSearchActivity); } + // Find the default web search provider. + ComponentName webSearchActivity = getPreferredWebSearchActivity(); + SearchableInfo newDefaultSearchableForWebSearch = null; + if (webSearchActivity != null) { + newDefaultSearchableForWebSearch = newSearchablesMap.get(webSearchActivity); + } + if (newDefaultSearchableForWebSearch == null) { + Log.w(LOG_TAG, "No searchable info found for new default web search activity " + + webSearchActivity); + } + // Store a consistent set of new values synchronized (this) { mSearchablesMap = newSearchablesMap; mSearchablesList = newSearchablesList; mSearchablesInGlobalSearchList = newSearchablesInGlobalSearchList; + mSearchablesForWebSearchList = newSearchablesForWebSearchList; mDefaultSearchable = newDefaultSearchable; + mDefaultSearchableForWebSearch = newDefaultSearchableForWebSearch; } + + // Inform all listeners that the list of searchables has been updated. + mContext.sendBroadcast(new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED)); } - + + /** + * Checks if the given activity component is present in the system and if so makes it the + * preferred activity for handling ACTION_WEB_SEARCH. + * @param component Name of the component to check and set as preferred. + * @param action Intent action for which this activity is to be set as preferred. + * @return true if component was detected and set as preferred activity, false if not. + */ + private boolean setPreferredActivity(ComponentName component, String action) { + Log.d(LOG_TAG, "Checking component " + component); + PackageManager pm = mContext.getPackageManager(); + ActivityInfo ai; + try { + ai = pm.getActivityInfo(component, 0); + } catch (PackageManager.NameNotFoundException e) { + return false; + } + + // The code here to find the value for bestMatch is heavily inspired by the code + // in ResolverActivity where the preferred activity is set. + Intent intent = new Intent(action); + intent.addCategory(Intent.CATEGORY_DEFAULT); + List<ResolveInfo> webSearchActivities = pm.queryIntentActivities(intent, 0); + ComponentName set[] = new ComponentName[webSearchActivities.size()]; + int bestMatch = 0; + for (int i = 0; i < webSearchActivities.size(); ++i) { + ResolveInfo ri = webSearchActivities.get(i); + set[i] = new ComponentName(ri.activityInfo.packageName, + ri.activityInfo.name); + if (ri.match > bestMatch) bestMatch = ri.match; + } + + Log.d(LOG_TAG, "Setting preferred web search activity to " + component); + IntentFilter filter = new IntentFilter(action); + filter.addCategory(Intent.CATEGORY_DEFAULT); + pm.replacePreferredActivity(filter, bestMatch, set, component); + return true; + } + + public ComponentName getPreferredWebSearchActivity() { + // Check if we have a preferred web search activity. + Intent intent = new Intent(Intent.ACTION_WEB_SEARCH); + PackageManager pm = mContext.getPackageManager(); + ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); + + if (ri == null || ri.activityInfo.name.equals(ResolverActivity.class.getName())) { + Log.d(LOG_TAG, "No preferred activity set for action web search."); + + // The components in the providers array are checked in the order of declaration so the + // first one has the highest priority. If the component exists in the system it is set + // as the preferred activity to handle intent action web search. + String[] preferredActivities = mContext.getResources().getStringArray( + com.android.internal.R.array.default_web_search_providers); + for (String componentName : preferredActivities) { + ComponentName component = ComponentName.unflattenFromString(componentName); + if (setPreferredActivity(component, Intent.ACTION_WEB_SEARCH)) { + return component; + } + } + } + + if (ri == null) return null; + return new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name); + } + /** * Returns the list of searchable activities. */ @@ -246,11 +354,33 @@ public class Searchables { ArrayList<SearchableInfo> result = new ArrayList<SearchableInfo>(mSearchablesList); return result; } - + /** * Returns a list of the searchable activities that can be included in global search. */ public synchronized ArrayList<SearchableInfo> getSearchablesInGlobalSearchList() { return new ArrayList<SearchableInfo>(mSearchablesInGlobalSearchList); } + + /** + * Returns a list of the searchable activities that handle web searches. + */ + public synchronized ArrayList<SearchableInfo> getSearchablesForWebSearchList() { + return new ArrayList<SearchableInfo>(mSearchablesForWebSearchList); + } + + /** + * Returns the default searchable activity for web searches. + */ + public synchronized SearchableInfo getDefaultSearchableForWebSearch() { + return mDefaultSearchableForWebSearch; + } + + /** + * Sets the default searchable activity for web searches. + */ + public synchronized void setDefaultWebSearch(ComponentName component) { + setPreferredActivity(component, Intent.ACTION_WEB_SEARCH); + buildSearchableList(); + } } diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml index eb94812..9b9c2e4 100644 --- a/core/res/res/values/arrays.xml +++ b/core/res/res/values/arrays.xml @@ -125,4 +125,14 @@ <item><xliff:g id="id">sync_failing</xliff:g></item> <item><xliff:g id="id">ime</xliff:g></item> </string-array> + + <!-- Do not translate. Each string points to the component name of an ACTION_WEB_SEARCH + handling activity. On startup if there were no preferred ACTION_WEB_SEARCH handlers, + the first component from this list which is found to be installed is set as the + preferred activity. --> + <string-array name="default_web_search_providers"> + <item>com.google.android.providers.genie/.GenieLauncher</item> + <item>com.android.googlesearch/.GoogleSearch</item> + <item>com.android.websearch/.Search.1</item> + </string-array> </resources> |