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:
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user