From ee69ff4eaee9342843d5f25338288865dda2d36a Mon Sep 17 00:00:00 2001 From: Narayan Kamath Date: Tue, 28 Jun 2011 12:07:18 +0100 Subject: Make the system global search provider a user setting. Also, modify Searchables / SearchManagerService to honour the setting when it's set. Change-Id: Ia63351fff4fe28ee79ac8b9e30fdb8edc43f5534 --- core/java/android/app/ISearchManager.aidl | 2 + core/java/android/app/SearchManager.java | 27 ++++ core/java/android/provider/Settings.java | 13 ++ .../server/search/SearchManagerService.java | 34 +++++ core/java/android/server/search/Searchables.java | 145 ++++++++++++++++++--- 5 files changed, 200 insertions(+), 21 deletions(-) (limited to 'core') diff --git a/core/java/android/app/ISearchManager.aidl b/core/java/android/app/ISearchManager.aidl index cb03d2c..688cdfd 100644 --- a/core/java/android/app/ISearchManager.aidl +++ b/core/java/android/app/ISearchManager.aidl @@ -19,6 +19,7 @@ package android.app; import android.app.SearchableInfo; import android.app.ISearchManagerCallback; import android.content.ComponentName; +import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.os.Bundle; @@ -26,6 +27,7 @@ import android.os.Bundle; interface ISearchManager { SearchableInfo getSearchableInfo(in ComponentName launchActivity); List getSearchablesInGlobalSearch(); + List getGlobalSearchActivities(); ComponentName getGlobalSearchActivity(); ComponentName getWebSearchActivity(); } diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java index aab087f..85a2fa8 100644 --- a/core/java/android/app/SearchManager.java +++ b/core/java/android/app/SearchManager.java @@ -22,6 +22,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.pm.ResolveInfo; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; @@ -374,6 +375,17 @@ public class SearchManager = "android.search.action.SEARCHABLES_CHANGED"; /** + * Intent action to be broadcast to inform that the global search provider + * has changed. Normal components will have no need to handle this intent since + * they should be using API methods from this class to access the global search + * activity + * + * @hide + */ + public final static String INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED + = "android.search.action.GLOBAL_SEARCH_ACTIVITY_CHANGED"; + + /** * Intent action broadcasted to inform that the search settings have changed in some way. * Either searchables have been enabled or disabled, or a different web search provider * has been chosen. @@ -526,6 +538,21 @@ public class SearchManager } /** + * Returns a list of installed apps that handle the global search + * intent. + * + * @hide + */ + public List getGlobalSearchActivities() { + try { + return mService.getGlobalSearchActivities(); + } catch (RemoteException ex) { + Log.e(TAG, "getGlobalSearchActivities() failed: " + ex); + return null; + } + } + + /** * Gets the name of the global search activity. * * @hide diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index afff7e2..65babc2 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -20,6 +20,7 @@ package android.provider; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.app.SearchManager; import android.content.ComponentName; import android.content.ContentResolver; import android.content.ContentValues; @@ -3490,6 +3491,18 @@ public final class Settings { "sms_outgoing_check_max_count"; /** + * The global search provider chosen by the user (if multiple global + * search providers are installed). This will be the provider returned + * by {@link SearchManager#getGlobalSearchActivity()} if it's still + * installed. This setting is stored as a flattened component name as + * per {@link ComponentName#flattenToString()}. + * + * @hide + */ + public static final String SEARCH_GLOBAL_SEARCH_ACTIVITY = + "search_global_search_activity"; + + /** * The number of promoted sources in GlobalSearch. * @hide */ diff --git a/core/java/android/server/search/SearchManagerService.java b/core/java/android/server/search/SearchManagerService.java index 3826a01..79ade26 100644 --- a/core/java/android/server/search/SearchManagerService.java +++ b/core/java/android/server/search/SearchManagerService.java @@ -23,10 +23,14 @@ import android.app.SearchManager; import android.app.SearchableInfo; import android.content.BroadcastReceiver; import android.content.ComponentName; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ResolveInfo; +import android.database.ContentObserver; import android.os.Process; +import android.provider.Settings; import android.util.Log; import java.util.List; @@ -46,6 +50,8 @@ public class SearchManagerService extends ISearchManager.Stub { // This field is initialized lazily in getSearchables(), and then never modified. private Searchables mSearchables; + private ContentObserver mGlobalSearchObserver; + /** * Initializes the Search Manager service in the provided system context. * Only one instance of this object should be created! @@ -56,6 +62,8 @@ public class SearchManagerService extends ISearchManager.Stub { mContext = context; mContext.registerReceiver(new BootCompletedReceiver(), new IntentFilter(Intent.ACTION_BOOT_COMPLETED)); + mGlobalSearchObserver = new GlobalSearchProviderObserver( + mContext.getContentResolver()); } private synchronized Searchables getSearchables() { @@ -100,6 +108,28 @@ public class SearchManagerService extends ISearchManager.Stub { } } + class GlobalSearchProviderObserver extends ContentObserver { + private final ContentResolver mResolver; + + public GlobalSearchProviderObserver(ContentResolver resolver) { + super(null); + mResolver = resolver; + mResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.SEARCH_GLOBAL_SEARCH_ACTIVITY), + false /* notifyDescendants */, + this); + } + + @Override + public void onChange(boolean selfChange) { + getSearchables().buildSearchableList(); + Intent intent = new Intent(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + mContext.sendBroadcast(intent); + } + + } + // // Searchable activities API // @@ -126,6 +156,10 @@ public class SearchManagerService extends ISearchManager.Stub { return getSearchables().getSearchablesInGlobalSearchList(); } + public List getGlobalSearchActivities() { + return getSearchables().getGlobalSearchActivities(); + } + /** * Gets the name of the global search activity. */ diff --git a/core/java/android/server/search/Searchables.java b/core/java/android/server/search/Searchables.java index 279c17d..f24d52f 100644 --- a/core/java/android/server/search/Searchables.java +++ b/core/java/android/server/search/Searchables.java @@ -16,19 +16,23 @@ package android.server.search; -import android.Manifest; import android.app.SearchManager; import android.app.SearchableInfo; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Bundle; +import android.provider.Settings; +import android.text.TextUtils; import android.util.Log; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.List; @@ -50,7 +54,10 @@ public class Searchables { private HashMap mSearchablesMap = null; private ArrayList mSearchablesList = null; private ArrayList mSearchablesInGlobalSearchList = null; - private ComponentName mGlobalSearchActivity = null; + // Contains all installed activities that handle the global search + // intent. + private List mGlobalSearchActivities; + private ComponentName mCurrentGlobalSearchActivity = null; private ComponentName mWebSearchActivity = null; public static String GOOGLE_SEARCH_COMPONENT_NAME = @@ -224,8 +231,11 @@ public class Searchables { } } + List newGlobalSearchActivities = findGlobalSearchActivities(); + // Find the global search activity - ComponentName newGlobalSearchActivity = findGlobalSearchActivity(); + ComponentName newGlobalSearchActivity = findGlobalSearchActivity( + newGlobalSearchActivities); // Find the web search activity ComponentName newWebSearchActivity = findWebSearchActivity(newGlobalSearchActivity); @@ -235,38 +245,124 @@ public class Searchables { mSearchablesMap = newSearchablesMap; mSearchablesList = newSearchablesList; mSearchablesInGlobalSearchList = newSearchablesInGlobalSearchList; - mGlobalSearchActivity = newGlobalSearchActivity; + mGlobalSearchActivities = newGlobalSearchActivities; + mCurrentGlobalSearchActivity = newGlobalSearchActivity; mWebSearchActivity = newWebSearchActivity; } } + /** + * Returns a sorted list of installed search providers as per + * the following heuristics: + * + * (a) System apps are given priority over non system apps. + * (b) Among system apps and non system apps, the relative ordering + * is defined by their declared priority. + */ + private List findGlobalSearchActivities() { + // Step 1 : Query the package manager for a list + // of activities that can handle the GLOBAL_SEARCH intent. + Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH); + PackageManager pm = mContext.getPackageManager(); + List activities = + pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); + + if (activities != null && !activities.isEmpty()) { + // Step 2: Rank matching activities according to our heuristics. + Collections.sort(activities, GLOBAL_SEARCH_RANKER); + } + + return activities; + } /** * Finds the global search activity. - * - * This is currently implemented by returning the first activity that handles - * the GLOBAL_SEARCH intent and has the GLOBAL_SEARCH permission. If we allow - * more than one global search activity to be installed, this code must be changed. */ - private ComponentName findGlobalSearchActivity() { + private ComponentName findGlobalSearchActivity(List installed) { + // Fetch the global search provider from the system settings, + // and if it's still installed, return it. + final String searchProviderSetting = getGlobalSearchProviderSetting(); + if (!TextUtils.isEmpty(searchProviderSetting)) { + final ComponentName globalSearchComponent = ComponentName.unflattenFromString( + searchProviderSetting); + if (globalSearchComponent != null && isInstalled(globalSearchComponent)) { + return globalSearchComponent; + } + } + + return getDefaultGlobalSearchProvider(installed); + } + + /** + * Checks whether the global search provider with a given + * component name is installed on the system or not. This deals with + * cases such as the removal of an installed provider. + */ + private boolean isInstalled(ComponentName globalSearch) { Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH); + intent.setComponent(globalSearch); + PackageManager pm = mContext.getPackageManager(); List activities = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); - int count = activities == null ? 0 : activities.size(); - for (int i = 0; i < count; i++) { - ActivityInfo ai = activities.get(i).activityInfo; - if (pm.checkPermission(Manifest.permission.GLOBAL_SEARCH, - ai.packageName) == PackageManager.PERMISSION_GRANTED) { - return new ComponentName(ai.packageName, ai.name); + + if (activities != null && !activities.isEmpty()) { + return true; + } + + return false; + } + + private static final Comparator GLOBAL_SEARCH_RANKER = + new Comparator() { + @Override + public int compare(ResolveInfo lhs, ResolveInfo rhs) { + if (lhs == rhs) { + return 0; + } + boolean lhsSystem = isSystemApp(lhs); + boolean rhsSystem = isSystemApp(rhs); + + if (lhsSystem && !rhsSystem) { + return -1; + } else if (rhsSystem && !lhsSystem) { + return 1; } else { - Log.w(LOG_TAG, "Package " + ai.packageName + " wants to handle GLOBAL_SEARCH, " - + "but does not have the GLOBAL_SEARCH permission."); + // Either both system engines, or both non system + // engines. + // + // Note, this isn't a typo. Higher priority numbers imply + // higher priority, but are "lower" in the sort order. + return rhs.priority - lhs.priority; } } + }; + + /** + * @return true iff. the resolve info corresponds to a system application. + */ + private static final boolean isSystemApp(ResolveInfo res) { + return (res.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; + } + + /** + * Returns the highest ranked search provider as per the + * ranking defined in {@link #getGlobalSearchActivities()}. + */ + private ComponentName getDefaultGlobalSearchProvider(List providerList) { + if (providerList != null && !providerList.isEmpty()) { + ActivityInfo ai = providerList.get(0).activityInfo; + return new ComponentName(ai.packageName, ai.name); + } + Log.w(LOG_TAG, "No global search activity found"); return null; } + private String getGlobalSearchProviderSetting() { + return Settings.Secure.getString(mContext.getContentResolver(), + Settings.Secure.SEARCH_GLOBAL_SEARCH_ACTIVITY); + } + /** * Finds the web search activity. * @@ -281,9 +377,9 @@ public class Searchables { PackageManager pm = mContext.getPackageManager(); List activities = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); - int count = activities == null ? 0 : activities.size(); - for (int i = 0; i < count; i++) { - ActivityInfo ai = activities.get(i).activityInfo; + + if (activities != null && !activities.isEmpty()) { + ActivityInfo ai = activities.get(0).activityInfo; // TODO: do some sanity checks here? return new ComponentName(ai.packageName, ai.name); } @@ -307,10 +403,17 @@ public class Searchables { } /** + * Returns a list of activities that handle the global search intent. + */ + public synchronized ArrayList getGlobalSearchActivities() { + return new ArrayList(mGlobalSearchActivities); + } + + /** * Gets the name of the global search activity. */ public synchronized ComponentName getGlobalSearchActivity() { - return mGlobalSearchActivity; + return mCurrentGlobalSearchActivity; } /** -- cgit v1.1