Include web search providers in Searchables.

- Along with ACTION_SEARCH we now enumate ACTION_WEB_SEARCH as well so web search providers are covered in the searchables list. This fixes a broken unit test.
- Moved get/setPreferredWebSearchActivity and get-all-web-search-providers implementation to this module when the searchables list gets updated, so that it happens on boot and on package add/remove events and remains up to date. The duplicate code in WebSearchProvider will be removed in a separate change.
- Also made Searchables broadcast an intent when the searchables list got rebuilt, so components such as GlobalSearch/SuggestionSources no longer need to do this on their own.
diff --git a/core/java/android/app/ISearchManager.aidl b/core/java/android/app/ISearchManager.aidl
index 39eb4f1..374423e 100644
--- a/core/java/android/app/ISearchManager.aidl
+++ b/core/java/android/app/ISearchManager.aidl
@@ -23,4 +23,7 @@
 interface ISearchManager {
    SearchableInfo getSearchableInfo(in ComponentName launchActivity, boolean globalSearch);
    List<SearchableInfo> getSearchablesInGlobalSearch();
+   List<SearchableInfo> getSearchablesForWebSearch();
+   SearchableInfo getDefaultSearchableForWebSearch();
+   void setDefaultWebSearch(in ComponentName component);
 }
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index b0c248c5..f1cc24a 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -1417,6 +1417,16 @@
             = "android.search.action.WEB_SEARCH_SETTINGS";
 
     /**
+     * Intent action broadcasted to inform that the searchables list or default have changed.
+     * Components should handle this intent if they cache any searchable data and wish to stay
+     * up to date on changes.
+     *
+     * @hide Pending API council approval.
+     */
+    public final static String INTENT_ACTION_SEARCHABLES_CHANGED
+            = "android.search.action.SEARCHABLES_CHANGED";
+
+    /**
      * If a suggestion has this value in {@link #SUGGEST_COLUMN_INTENT_ACTION},
      * the search dialog will take no action.
      *
@@ -1744,4 +1754,48 @@
             return null;
         }
     }
+
+    /**
+     * Returns a list of the searchable activities that handle web searches.
+     *
+     * @return a a list of all searchable activities that handle {@link SearchManager#ACTION_WEB_SEARCH}.
+     *
+     * @hide because SearchableInfo is not part of the API.
+     */
+    public static List<SearchableInfo> getSearchablesForWebSearch() {
+        try {
+            return sService.getSearchablesForWebSearch();
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Returns the default searchable activity for web searches.
+     *
+     * @return searchable information for the activity handling web searches by default.
+     *
+     * @hide because SearchableInfo is not part of the API.
+     */
+    public static SearchableInfo getDefaultSearchableForWebSearch() {
+        try {
+            return sService.getDefaultSearchableForWebSearch();
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Sets the default searchable activity for web searches.
+     *
+     * @param component Name of the component to set as default activity for web searches.
+     *
+     * @hide
+     */
+    public static void setDefaultWebSearch(ComponentName component) {
+        try {
+            sService.setDefaultWebSearch(component);
+        } catch (RemoteException e) {
+        }
+    }
 }
diff --git a/core/java/android/server/search/SearchManagerService.java b/core/java/android/server/search/SearchManagerService.java
index 952372f..060bcea 100644
--- a/core/java/android/server/search/SearchManagerService.java
+++ b/core/java/android/server/search/SearchManagerService.java
@@ -23,12 +23,13 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.Handler;
+import android.os.RemoteException;
 
 import java.util.List;
 
 /**
  * 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).
  */
@@ -43,19 +44,19 @@
     private final Handler mHandler;
     private boolean mSearchablesDirty;
     private Searchables mSearchables;
-    
+
     /**
      * 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);
-        
+
         // Setup the infrastructure for updating and maintaining the list
         // of searchable activities.
         IntentFilter filter = new IntentFilter();
@@ -64,15 +65,15 @@
         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.
         mHandler.post(mRunUpdateSearchable);
     }
-    
+
     /**
      * Listens for intent broadcasts.
-     * 
+     *
      * The primary purpose here is to refresh the "searchables" list
      * if packages are added/removed.
      */
@@ -80,7 +81,7 @@
         @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) ||
@@ -91,14 +92,14 @@
             }
         }
     };
-    
+
     /**
      * This runnable (for the main handler / UI thread) will update the searchables list.
      */
     private Runnable mRunUpdateSearchable = new Runnable() {
         public void run() {
             updateSearchablesIfDirty();
-        } 
+        }
     };
 
     /**
@@ -124,7 +125,7 @@
      *
      * @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,
@@ -141,7 +142,7 @@
 
         return si;
     }
-    
+
     /**
      * Returns a list of the searchable activities that can be included in global search.
      */
@@ -150,4 +151,26 @@
         return mSearchables.getSearchablesInGlobalSearchList();
     }
 
+    /**
+     * 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);
+    }
 }
diff --git a/core/java/android/server/search/Searchables.java b/core/java/android/server/search/Searchables.java
index 62631d6..a27667b 100644
--- a/core/java/android/server/search/Searchables.java
+++ b/core/java/android/server/search/Searchables.java
@@ -16,13 +16,18 @@
 
 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;
 
@@ -31,37 +36,39 @@
 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;
+
     /**
-     * 
+     *
      * @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
@@ -73,16 +80,16 @@
      * 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) {
@@ -92,18 +99,18 @@
             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) {
@@ -116,11 +123,11 @@
                     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();
@@ -146,80 +153,101 @@
         } 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);
@@ -230,15 +258,95 @@
                     + 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;
+                }
+            }
+        }
+
+        if (ri == null) return null;
+        return new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name);
+    }
+
     /**
      * Returns the list of searchable activities.
      */
@@ -246,11 +354,33 @@
         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();
+    }
 }
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index eb94812..9b9c2e4 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -125,4 +125,14 @@
         <item><xliff:g id="id">sync_failing</xliff:g></item>
         <item><xliff:g id="id">ime</xliff:g></item>
     </string-array>
+
+    <!-- Do not translate. Each string points to the component name of an ACTION_WEB_SEARCH
+         handling activity. On startup if there were no preferred ACTION_WEB_SEARCH handlers,
+         the first component from this list which is found to be installed is set as the
+         preferred activity. -->
+    <string-array name="default_web_search_providers">
+        <item>com.google.android.providers.genie/.GenieLauncher</item>
+        <item>com.android.googlesearch/.GoogleSearch</item>
+        <item>com.android.websearch/.Search.1</item>
+    </string-array>
 </resources>
diff --git a/tests/AndroidTests/src/com/android/unit_tests/SearchablesTest.java b/tests/AndroidTests/src/com/android/unit_tests/SearchablesTest.java
index bdf67ba..a46f07d 100644
--- a/tests/AndroidTests/src/com/android/unit_tests/SearchablesTest.java
+++ b/tests/AndroidTests/src/com/android/unit_tests/SearchablesTest.java
@@ -361,7 +361,8 @@
         @Override 
         public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {
             assertNotNull(intent);
-            assertEquals(intent.getAction(), Intent.ACTION_SEARCH);
+            assertTrue(intent.getAction().equals(Intent.ACTION_SEARCH)
+                    || intent.getAction().equals(Intent.ACTION_WEB_SEARCH));
             switch (mSearchablesMode) {
             case SEARCHABLES_PASSTHROUGH:
                 return mRealPackageManager.queryIntentActivities(intent, flags);