summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authorNarayan Kamath <narayan@google.com>2011-06-28 12:07:18 +0100
committerNarayan Kamath <narayan@google.com>2011-06-28 15:30:49 +0100
commitee69ff4eaee9342843d5f25338288865dda2d36a (patch)
treef7a35649b1a716ed03a48014b6b12a6f00c06641 /core
parent7ae28d321684ea71fa41b2dc4caca14c6d964984 (diff)
downloadframeworks_base-ee69ff4eaee9342843d5f25338288865dda2d36a.zip
frameworks_base-ee69ff4eaee9342843d5f25338288865dda2d36a.tar.gz
frameworks_base-ee69ff4eaee9342843d5f25338288865dda2d36a.tar.bz2
Make the system global search provider a user setting.
Also, modify Searchables / SearchManagerService to honour the setting when it's set. Change-Id: Ia63351fff4fe28ee79ac8b9e30fdb8edc43f5534
Diffstat (limited to 'core')
-rw-r--r--core/java/android/app/ISearchManager.aidl2
-rw-r--r--core/java/android/app/SearchManager.java27
-rw-r--r--core/java/android/provider/Settings.java13
-rw-r--r--core/java/android/server/search/SearchManagerService.java34
-rw-r--r--core/java/android/server/search/Searchables.java145
5 files changed, 200 insertions, 21 deletions
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<SearchableInfo> getSearchablesInGlobalSearch();
+ List<ResolveInfo> 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<ResolveInfo> 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<ResolveInfo> 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<ComponentName, SearchableInfo> mSearchablesMap = null;
private ArrayList<SearchableInfo> mSearchablesList = null;
private ArrayList<SearchableInfo> mSearchablesInGlobalSearchList = null;
- private ComponentName mGlobalSearchActivity = null;
+ // Contains all installed activities that handle the global search
+ // intent.
+ private List<ResolveInfo> mGlobalSearchActivities;
+ private ComponentName mCurrentGlobalSearchActivity = null;
private ComponentName mWebSearchActivity = null;
public static String GOOGLE_SEARCH_COMPONENT_NAME =
@@ -224,8 +231,11 @@ public class Searchables {
}
}
+ List<ResolveInfo> 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<ResolveInfo> 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<ResolveInfo> 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<ResolveInfo> 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<ResolveInfo> 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<ResolveInfo> GLOBAL_SEARCH_RANKER =
+ new Comparator<ResolveInfo>() {
+ @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<ResolveInfo> 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<ResolveInfo> 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<ResolveInfo> getGlobalSearchActivities() {
+ return new ArrayList<ResolveInfo>(mGlobalSearchActivities);
+ }
+
+ /**
* Gets the name of the global search activity.
*/
public synchronized ComponentName getGlobalSearchActivity() {
- return mGlobalSearchActivity;
+ return mCurrentGlobalSearchActivity;
}
/**