diff options
Diffstat (limited to 'core/java/android/server')
-rw-r--r-- | core/java/android/server/BluetoothDeviceService.java | 6 | ||||
-rw-r--r-- | core/java/android/server/search/SearchManagerService.java | 289 | ||||
-rw-r--r-- | core/java/android/server/search/SearchableInfo.java | 54 | ||||
-rw-r--r-- | core/java/android/server/search/Searchables.java | 254 |
4 files changed, 512 insertions, 91 deletions
diff --git a/core/java/android/server/BluetoothDeviceService.java b/core/java/android/server/BluetoothDeviceService.java index 8e5cee9..8c843ef 100644 --- a/core/java/android/server/BluetoothDeviceService.java +++ b/core/java/android/server/BluetoothDeviceService.java @@ -372,6 +372,10 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { mEventLoop.onModeChanged(getModeNative()); } + if (mIsAirplaneSensitive && isAirplaneModeOn()) { + disable(false); + } + } } @@ -1220,6 +1224,8 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { break; } pw.println("getHeadsetAddress() = " + headset.getHeadsetAddress()); + pw.println("getBatteryUsageHint() = " + headset.getBatteryUsageHint()); + headset.close(); } diff --git a/core/java/android/server/search/SearchManagerService.java b/core/java/android/server/search/SearchManagerService.java index 03623d6..373e61f 100644 --- a/core/java/android/server/search/SearchManagerService.java +++ b/core/java/android/server/search/SearchManagerService.java @@ -17,48 +17,69 @@ package android.server.search; import android.app.ISearchManager; +import android.app.ISearchManagerCallback; +import android.app.SearchDialog; +import android.app.SearchManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; +import android.content.res.Configuration; +import android.os.Bundle; import android.os.Handler; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.text.TextUtils; +import android.util.Log; import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; /** * 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). */ public class SearchManagerService extends ISearchManager.Stub + implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener { // general debugging support private static final String TAG = "SearchManagerService"; - private static final boolean DEBUG = false; - - // configuration choices - private static final boolean IMMEDIATE_SEARCHABLES_UPDATE = true; + private static final boolean DBG = false; // class maintenance and general shared data private final Context mContext; private final Handler mHandler; private boolean mSearchablesDirty; - private Searchables mSearchables; - + private final Searchables mSearchables; + + final SearchDialog mSearchDialog; + ISearchManagerCallback mCallback = null; + + private final boolean mDisabledOnBoot; + + private static final String DISABLE_SEARCH_PROPERTY = "dev.disablesearchdialog"; + /** * 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); - + mSearchDialog = new SearchDialog(context); + mSearchDialog.setOnCancelListener(this); + mSearchDialog.setOnDismissListener(this); + // Setup the infrastructure for updating and maintaining the list // of searchable activities. IntentFilter filter = new IntentFilter(); @@ -67,17 +88,18 @@ 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. - if (IMMEDIATE_SEARCHABLES_UPDATE) { - mHandler.post(mRunUpdateSearchable); - } + mHandler.post(mRunUpdateSearchable); + + // allows disabling of search dialog for stress testing runs + mDisabledOnBoot = !TextUtils.isEmpty(SystemProperties.get(DISABLE_SEARCH_PROPERTY)); } - + /** * Listens for intent broadcasts. - * + * * The primary purpose here is to refresh the "searchables" list * if packages are added/removed. */ @@ -85,29 +107,25 @@ 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) || action.equals(Intent.ACTION_PACKAGE_CHANGED)) { mSearchablesDirty = true; - if (IMMEDIATE_SEARCHABLES_UPDATE) { - mHandler.post(mRunUpdateSearchable); - } + mHandler.post(mRunUpdateSearchable); return; } } }; - + /** * This runnable (for the main handler / UI thread) will update the searchables list. */ private Runnable mRunUpdateSearchable = new Runnable() { public void run() { - if (mSearchablesDirty) { - updateSearchables(); - } - } + updateSearchablesIfDirty(); + } }; /** @@ -115,42 +133,251 @@ public class SearchManagerService extends ISearchManager.Stub * a package add/remove broadcast message. */ private void updateSearchables() { + if (DBG) debug("updateSearchables()"); mSearchables.buildSearchableList(); mSearchablesDirty = false; } /** + * Updates the list of searchables if needed. + */ + private void updateSearchablesIfDirty() { + if (mSearchablesDirty) { + updateSearchables(); + } + } + + /** * Returns the SearchableInfo for a given activity * * @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, * or null if no searchable metadata was available. */ public SearchableInfo getSearchableInfo(ComponentName launchActivity, boolean globalSearch) { - // final check. however we should try to avoid this, because - // it slows down the entry into the UI. - if (mSearchablesDirty) { - updateSearchables(); - } + updateSearchablesIfDirty(); SearchableInfo si = null; if (globalSearch) { si = mSearchables.getDefaultSearchable(); } else { + if (launchActivity == null) { + Log.e(TAG, "getSearchableInfo(), activity == null"); + return null; + } si = mSearchables.getSearchableInfo(launchActivity); } return si; } - + /** * Returns a list of the searchable activities that can be included in global search. */ public List<SearchableInfo> getSearchablesInGlobalSearch() { + updateSearchablesIfDirty(); return mSearchables.getSearchablesInGlobalSearchList(); } + /** + * Launches the search UI on the main thread of the service. + * + * @see SearchManager#startSearch(String, boolean, ComponentName, Bundle, boolean) + */ + public void startSearch(final String initialQuery, + final boolean selectInitialQuery, + final ComponentName launchActivity, + final Bundle appSearchData, + final boolean globalSearch, + final ISearchManagerCallback searchManagerCallback) { + if (DBG) debug("startSearch()"); + Runnable task = new Runnable() { + public void run() { + performStartSearch(initialQuery, + selectInitialQuery, + launchActivity, + appSearchData, + globalSearch, + searchManagerCallback); + } + }; + mHandler.post(task); + } + + /** + * Actually launches the search. This must be called on the service UI thread. + */ + /*package*/ void performStartSearch(String initialQuery, + boolean selectInitialQuery, + ComponentName launchActivity, + Bundle appSearchData, + boolean globalSearch, + ISearchManagerCallback searchManagerCallback) { + if (DBG) debug("performStartSearch()"); + + if (mDisabledOnBoot) { + Log.d(TAG, "ignoring start search request because " + DISABLE_SEARCH_PROPERTY + + " system property is set."); + return; + } + + mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData, + globalSearch); + if (searchManagerCallback != null) { + mCallback = searchManagerCallback; + } + } + + /** + * Cancels the search dialog. Can be called from any thread. + */ + public void stopSearch() { + if (DBG) debug("stopSearch()"); + mHandler.post(new Runnable() { + public void run() { + performStopSearch(); + } + }); + } + + /** + * Cancels the search dialog. Must be called from the service UI thread. + */ + /*package*/ void performStopSearch() { + if (DBG) debug("performStopSearch()"); + mSearchDialog.cancel(); + } + + /** + * Determines if the Search UI is currently displayed. + * + * @see SearchManager#isVisible() + */ + public boolean isVisible() { + return postAndWait(mIsShowing, false, "isShowing()"); + } + + private final Callable<Boolean> mIsShowing = new Callable<Boolean>() { + public Boolean call() { + return mSearchDialog.isShowing(); + } + }; + + public Bundle onSaveInstanceState() { + return postAndWait(mOnSaveInstanceState, null, "onSaveInstanceState()"); + } + + private final Callable<Bundle> mOnSaveInstanceState = new Callable<Bundle>() { + public Bundle call() { + if (mSearchDialog.isShowing()) { + return mSearchDialog.onSaveInstanceState(); + } else { + return null; + } + } + }; + + public void onRestoreInstanceState(final Bundle searchDialogState) { + if (searchDialogState != null) { + mHandler.post(new Runnable() { + public void run() { + mSearchDialog.onRestoreInstanceState(searchDialogState); + } + }); + } + } + + public void onConfigurationChanged(final Configuration newConfig) { + mHandler.post(new Runnable() { + public void run() { + if (mSearchDialog.isShowing()) { + mSearchDialog.onConfigurationChanged(newConfig); + } + } + }); + } + + /** + * Called by {@link SearchDialog} when it goes away. + */ + public void onDismiss(DialogInterface dialog) { + if (DBG) debug("onDismiss()"); + if (mCallback != null) { + try { + mCallback.onDismiss(); + } catch (RemoteException ex) { + Log.e(TAG, "onDismiss() failed: " + ex); + } + } + } + + /** + * Called by {@link SearchDialog} when the user or activity cancels search. + * When this is called, {@link #onDismiss} is called too. + */ + public void onCancel(DialogInterface dialog) { + if (DBG) debug("onCancel()"); + if (mCallback != null) { + try { + mCallback.onCancel(); + } catch (RemoteException ex) { + Log.e(TAG, "onCancel() failed: " + ex); + } + } + } + + /** + * 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); + } + + /** + * Runs an operation on the handler for the service, blocks until it returns, + * and returns the value returned by the operation. + * + * @param <V> Return value type. + * @param callable Operation to run. + * @param errorResult Value to return if the operations throws an exception. + * @param name Operation name to include in error log messages. + * @return The value returned by the operation. + */ + private <V> V postAndWait(Callable<V> callable, V errorResult, String name) { + FutureTask<V> task = new FutureTask<V>(callable); + mHandler.post(task); + try { + return task.get(); + } catch (InterruptedException ex) { + Log.e(TAG, "Error calling " + name + ": " + ex); + return errorResult; + } catch (ExecutionException ex) { + Log.e(TAG, "Error calling " + name + ": " + ex); + return errorResult; + } + } + + private static void debug(String msg) { + Thread thread = Thread.currentThread(); + Log.d(TAG, msg + " (" + thread.getName() + "-" + thread.getId() + ")"); + } } diff --git a/core/java/android/server/search/SearchableInfo.java b/core/java/android/server/search/SearchableInfo.java index 842fc75..8ef1f15 100644 --- a/core/java/android/server/search/SearchableInfo.java +++ b/core/java/android/server/search/SearchableInfo.java @@ -40,7 +40,7 @@ import java.util.HashMap; public final class SearchableInfo implements Parcelable { // general debugging support - private static final boolean DBG = true; + private static final boolean DBG = false; private static final String LOG_TAG = "SearchableInfo"; // static strings used for XML lookups. @@ -66,6 +66,8 @@ public final class SearchableInfo implements Parcelable { private final int mSearchInputType; private final int mSearchImeOptions; private final boolean mIncludeInGlobalSearch; + private final boolean mQueryAfterZeroResults; + private final String mSettingsDescription; private final String mSuggestAuthority; private final String mSuggestPath; private final String mSuggestSelection; @@ -133,6 +135,14 @@ public final class SearchableInfo implements Parcelable { public boolean shouldRewriteQueryFromText() { return 0 != (mSearchMode & SEARCH_MODE_QUERY_REWRITE_FROM_TEXT); } + + /** + * Gets the description to use for this source in system search settings, or null if + * none has been specified. + */ + public String getSettingsDescription() { + return mSettingsDescription; + } /** * Retrieve the path for obtaining search suggestions. @@ -276,7 +286,11 @@ public final class SearchableInfo implements Parcelable { EditorInfo.IME_ACTION_SEARCH); mIncludeInGlobalSearch = a.getBoolean( com.android.internal.R.styleable.Searchable_includeInGlobalSearch, false); + mQueryAfterZeroResults = a.getBoolean( + com.android.internal.R.styleable.Searchable_queryAfterZeroResults, false); + mSettingsDescription = a.getString( + com.android.internal.R.styleable.Searchable_searchSettingsDescription); mSuggestAuthority = a.getString( com.android.internal.R.styleable.Searchable_searchSuggestAuthority); mSuggestPath = a.getString( @@ -317,7 +331,7 @@ public final class SearchableInfo implements Parcelable { // for now, implement some form of rules - minimal data if (mLabelId == 0) { - throw new IllegalArgumentException("No label."); + throw new IllegalArgumentException("Search label must be a resource reference."); } } @@ -438,13 +452,18 @@ public final class SearchableInfo implements Parcelable { xml.close(); if (DBG) { - Log.d(LOG_TAG, "Checked " + activityInfo.name - + ",label=" + searchable.getLabelId() - + ",icon=" + searchable.getIconId() - + ",suggestAuthority=" + searchable.getSuggestAuthority() - + ",target=" + searchable.getSearchActivity().getClassName() - + ",global=" + searchable.shouldIncludeInGlobalSearch() - + ",threshold=" + searchable.getSuggestThreshold()); + if (searchable != null) { + Log.d(LOG_TAG, "Checked " + activityInfo.name + + ",label=" + searchable.getLabelId() + + ",icon=" + searchable.getIconId() + + ",suggestAuthority=" + searchable.getSuggestAuthority() + + ",target=" + searchable.getSearchActivity().getClassName() + + ",global=" + searchable.shouldIncludeInGlobalSearch() + + ",settingsDescription=" + searchable.getSettingsDescription() + + ",threshold=" + searchable.getSuggestThreshold()); + } else { + Log.d(LOG_TAG, "Checked " + activityInfo.name + ", no searchable meta-data"); + } } return searchable; } @@ -637,6 +656,17 @@ public final class SearchableInfo implements Parcelable { } /** + * Checks whether this searchable activity should be invoked after a query returned zero + * results. + * + * @return The value of the <code>queryAfterZeroResults</code> attribute, + * or <code>false</code> if the attribute is not set. + */ + public boolean queryAfterZeroResults() { + return mQueryAfterZeroResults; + } + + /** * Support for parcelable and aidl operations. */ public static final Parcelable.Creator<SearchableInfo> CREATOR @@ -667,7 +697,9 @@ public final class SearchableInfo implements Parcelable { mSearchInputType = in.readInt(); mSearchImeOptions = in.readInt(); mIncludeInGlobalSearch = in.readInt() != 0; - + mQueryAfterZeroResults = in.readInt() != 0; + + mSettingsDescription = in.readString(); mSuggestAuthority = in.readString(); mSuggestPath = in.readString(); mSuggestSelection = in.readString(); @@ -702,7 +734,9 @@ public final class SearchableInfo implements Parcelable { dest.writeInt(mSearchInputType); dest.writeInt(mSearchImeOptions); dest.writeInt(mIncludeInGlobalSearch ? 1 : 0); + dest.writeInt(mQueryAfterZeroResults ? 1 : 0); + dest.writeString(mSettingsDescription); dest.writeString(mSuggestAuthority); dest.writeString(mSuggestPath); dest.writeString(mSuggestSelection); diff --git a/core/java/android/server/search/Searchables.java b/core/java/android/server/search/Searchables.java index 9586d56..c7cc8ed 100644 --- a/core/java/android/server/search/Searchables.java +++ b/core/java/android/server/search/Searchables.java @@ -16,49 +16,64 @@ 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; import java.util.ArrayList; 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; + + public static String GOOGLE_SEARCH_COMPONENT_NAME = + "com.android.googlesearch/.GoogleSearch"; + public static String ENHANCED_GOOGLE_SEARCH_COMPONENT_NAME = + "com.google.android.providers.enhancedgooglesearch/.Launcher"; + /** - * + * * @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 @@ -70,16 +85,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) { @@ -89,18 +104,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) { @@ -113,11 +128,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(); @@ -143,95 +158,212 @@ 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); SearchableInfo newDefaultSearchable = newSearchablesMap.get(globalSearchActivity); + if (newDefaultSearchable == null) { + Log.w(LOG_TAG, "No searchable info found for new default searchable activity " + + 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; + } + } + } else { + // If the current preferred activity is GoogleSearch, and we detect + // EnhancedGoogleSearch installed as well, set the latter as preferred since that + // is a superset and provides more functionality. + ComponentName cn = new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name); + if (cn.flattenToShortString().equals(GOOGLE_SEARCH_COMPONENT_NAME)) { + ComponentName enhancedGoogleSearch = ComponentName.unflattenFromString( + ENHANCED_GOOGLE_SEARCH_COMPONENT_NAME); + if (setPreferredActivity(enhancedGoogleSearch, Intent.ACTION_WEB_SEARCH)) { + return enhancedGoogleSearch; + } + } } + + if (ri == null) return null; + return new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name); } - + /** * Returns the list of searchable activities. */ @@ -239,11 +371,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(); + } } |