Make the system global search provider a user setting.

Also, modify Searchables / SearchManagerService to
honour the setting when it's set.

Change-Id: Ia63351fff4fe28ee79ac8b9e30fdb8edc43f5534
This commit is contained in:
Narayan Kamath
2011-06-28 12:07:18 +01:00
parent 7ae28d3216
commit ee69ff4eae
5 changed files with 202 additions and 23 deletions

View File

@@ -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();
}

View File

@@ -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;
@@ -373,6 +374,17 @@ public class SearchManager
public final static String INTENT_ACTION_SEARCHABLES_CHANGED
= "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
@@ -525,6 +537,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.
*

View File

@@ -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;
@@ -3489,6 +3490,18 @@ public final class Settings {
public static final String SMS_OUTGOING_CHECK_MAX_COUNT =
"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

View File

@@ -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.
*/

View File

@@ -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;
}
}
/**
* Finds the global search activity.
* Returns a sorted list of installed search providers as per
* the following heuristics:
*
* 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.
* (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 ComponentName findGlobalSearchActivity() {
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);
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);
} else {
Log.w(LOG_TAG, "Package " + ai.packageName + " wants to handle GLOBAL_SEARCH, "
+ "but does not have the GLOBAL_SEARCH permission.");
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.
*/
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);
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 {
// 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);
}
@@ -306,11 +402,18 @@ public class Searchables {
return new ArrayList<SearchableInfo>(mSearchablesInGlobalSearchList);
}
/**
* 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;
}
/**