Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cfe79e92 authored by Bjorn Bringert's avatar Bjorn Bringert Committed by Android (Google) Code Review
Browse files

Merge "Clean up global search and web search activity finding"

parents d162a114 6cf7a325
Loading
Loading
Loading
Loading
+3 −4
Original line number Diff line number Diff line
@@ -24,9 +24,8 @@ import android.os.Bundle;

/** @hide */
interface ISearchManager {
   SearchableInfo getSearchableInfo(in ComponentName launchActivity, boolean globalSearch);
   SearchableInfo getSearchableInfo(in ComponentName launchActivity);
   List<SearchableInfo> getSearchablesInGlobalSearch();
   List<SearchableInfo> getSearchablesForWebSearch();
   SearchableInfo getDefaultSearchableForWebSearch();
   void setDefaultWebSearch(in ComponentName component);
   ComponentName getGlobalSearchActivity();
   ComponentName getWebSearchActivity();
}
+1 −1
Original line number Diff line number Diff line
@@ -274,7 +274,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
        SearchManager searchManager = (SearchManager)
                mContext.getSystemService(Context.SEARCH_SERVICE);
        // Try to get the searchable info for the provided component.
        mSearchable = searchManager.getSearchableInfo(componentName, false);
        mSearchable = searchManager.getSearchableInfo(componentName);

        if (mSearchable == null) {
            return false;
+16 −68
Original line number Diff line number Diff line
@@ -137,21 +137,11 @@ import java.util.List;
 * setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);   // search within your activity
 * setDefaultKeyMode(DEFAULT_KEYS_SEARCH_GLOBAL);  // search using platform global search</pre>
 * 
 * <p><b>How to enable global search with Quick Search Box.</b>  In addition to searching within
 * <p><b>How to start global search.</b>  In addition to searching within
 * your activity or application, you can also use the Search Manager to invoke a platform-global
 * search, which uses Quick Search Box to search across the device and the web. There are two ways
 * to do this:
 * <ul><li>You can simply define "search" within your application or activity to mean global search.
 * This is described in more detail in the 
 * <a href="#SearchabilityMetadata">Searchability Metadata</a> section.  Briefly, you will
 * add a single meta-data entry to your manifest, declaring that the default search
 * for your application is "*".  This indicates to the system that no application-specific
 * search activity is provided, and that it should launch web-based search instead.</li>
 * <li>Simply do nothing and the default implementation of
 * {@link android.app.Activity#onSearchRequested} will cause global search to be triggered.
 * (You can also always trigger search via a direct call to {@link android.app.Activity#startSearch}.
 * This is most useful if you wish to provide local searchability <i>and</i> access to global
 * search.)</li></ul> 
 * search, which uses Quick Search Box to search across the device and the web.
 * Override {@link android.app.Activity#onSearchRequested} and call
 * {@link android.app.Activity#startSearch} with {@code globalSearch} set to {@code true}.
 * 
 * <p><b>How to disable search from your activity.</b> Search is a system-wide feature and users
 * will expect it to be available in all contexts.  If your UI design absolutely precludes
@@ -871,12 +861,8 @@ import java.util.List;
 * 
 * <p>The simplest way to specify this is to add a <i>search reference</i> element to the
 * application entry in the <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a> file.  
 * The value of this reference can be either of:
 * <ul><li>The name of your searchable activity.  
 * It is typically prefixed by '.' to indicate that it's in the same package.</li>
 * <li>A "*" indicates that the system may select a default searchable activity, in which
 * case it will typically select web-based search.</li>
 * </ul>
 * The value of this reference should be the name of your searchable activity.
 * It is typically prefixed by '.' to indicate that it's in the same package.
 *
 * <p>Here is a snippet showing the necessary addition to the manifest entry for your 
 * non-searchable activities.
@@ -1662,33 +1648,16 @@ public class SearchManager
    /**
     * Gets the name of 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 acitivity to be installed, this code must be changed.
     *
     * TODO: Doing this every time we start global search is inefficient. Will fix that once
     * we have settled on the right mechanism for finding the global search activity.
     *
     * @hide
     */
    public ComponentName getGlobalSearchActivity() {
        Intent intent = new Intent(INTENT_ACTION_GLOBAL_SEARCH);
        PackageManager pm = mContext.getPackageManager();
        List<ResolveInfo> activities =
                pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
        int count = 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(TAG, "Package " + ai.packageName + " wants to handle GLOBAL_SEARCH, "
                        + "but does not have the GLOBAL_SEARCH permission.");
            }
        }
        try {
            return mService.getGlobalSearchActivity();
        } catch (RemoteException ex) {
            Log.e(TAG, "getGlobalSearchActivity() failed: " + ex);
            return null;
        }
    }

    /**
     * Gets the name of the web search activity.
@@ -1700,13 +1669,12 @@ public class SearchManager
     * @hide
     */
    public ComponentName getWebSearchActivity() {
        ComponentName globalSearch = getGlobalSearchActivity();
        if (globalSearch == null) {
        try {
            return mService.getWebSearchActivity();
        } catch (RemoteException ex) {
            Log.e(TAG, "getWebSearchActivity() failed: " + ex);
            return null;
        }
        Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
        intent.setPackage(globalSearch.getPackageName());
        return intent.resolveActivity(mContext.getPackageManager());
    }

    /**
@@ -1840,27 +1808,7 @@ public class SearchManager
     */
    public SearchableInfo getSearchableInfo(ComponentName componentName) {
        try {
            return mService.getSearchableInfo(componentName, false);
        } catch (RemoteException ex) {
            Log.e(TAG, "getSearchableInfo() failed: " + ex);
            return null;
        }
    }

    /**
     * Gets information about a searchable activity.
     *
     * @param componentName The activity to get searchable information for.
     * @param globalSearch If <code>false</code>, return information about the given activity.
     *        If <code>true</code>, return information about the global search activity. 
     * @return Searchable information, or <code>null</code> if the activity is not searchable.
     * 
     * @hide because SearchableInfo is not part of the API.
     */
    public SearchableInfo getSearchableInfo(ComponentName componentName,
            boolean globalSearch) {
        try {
            return mService.getSearchableInfo(componentName, globalSearch);
            return mService.getSearchableInfo(componentName);
        } catch (RemoteException ex) {
            Log.e(TAG, "getSearchableInfo() failed: " + ex);
            return null;
+11 −30
Original line number Diff line number Diff line
@@ -123,25 +123,16 @@ public class SearchManagerService extends ISearchManager.Stub {
     * 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
     * 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(final ComponentName launchActivity,
            final boolean globalSearch) {
        if (globalSearch) {
            return getSearchables().getDefaultSearchable();
        } else {
    public SearchableInfo getSearchableInfo(final ComponentName launchActivity) {
        if (launchActivity == null) {
            Log.e(TAG, "getSearchableInfo(), activity == null");
            return null;
        }
        return getSearchables().getSearchableInfo(launchActivity);
    }
    }

    /**
     * Returns a list of the searchable activities that can be included in global search.
@@ -151,27 +142,17 @@ public class SearchManagerService extends ISearchManager.Stub {
    }

    /**
     * Returns a list of the searchable activities that handle web searches.
     * Can be called from any thread.
     * Gets the name of the global search activity.
     */
    public List<SearchableInfo> getSearchablesForWebSearch() {
        return getSearchables().getSearchablesForWebSearchList();
    public ComponentName getGlobalSearchActivity() {
        return getSearchables().getGlobalSearchActivity();
    }

    /**
     * Returns the default searchable activity for web searches.
     * Can be called from any thread.
     * Gets the name of the web search activity.
     */
    public SearchableInfo getDefaultSearchableForWebSearch() {
        return getSearchables().getDefaultSearchableForWebSearch();
    public ComponentName getWebSearchActivity() {
        return getSearchables().getWebSearchActivity();
    }

    /**
     * Sets the default searchable activity for web searches.
     * Can be called from any thread.
     */
    public void setDefaultWebSearch(final ComponentName component) {
        getSearchables().setDefaultWebSearch(component);
        broadcastSearchablesChanged();
    }
}
+59 −143
Original line number Diff line number Diff line
@@ -16,14 +16,12 @@

package android.server.search;

import com.android.internal.app.ResolverActivity;

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.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -52,9 +50,8 @@ public class Searchables {
    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;
    private ComponentName mGlobalSearchActivity = null;
    private ComponentName mWebSearchActivity = null;

    public static String GOOGLE_SEARCH_COMPONENT_NAME =
            "com.android.googlesearch/.GoogleSearch";
@@ -131,10 +128,9 @@ public class Searchables {
            // Irrespective of source, if a reference was found, follow it.
            if (refActivityName != null)
            {
                // An app or activity can declare that we should simply launch
                // "system default search" if search is invoked.
                // This value is deprecated, return null
                if (refActivityName.equals(MD_SEARCHABLE_SYSTEM_SEARCH)) {
                    return getDefaultSearchable();
                    return null;
                }
                String pkg = activity.getPackageName();
                ComponentName referredActivity;
@@ -163,20 +159,6 @@ public class Searchables {

    }

    /**
     * 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
@@ -205,8 +187,6 @@ public class Searchables {
                                = new ArrayList<SearchableInfo>();
        ArrayList<SearchableInfo> newSearchablesInGlobalSearchList
                                = new ArrayList<SearchableInfo>();
        ArrayList<SearchableInfo> newSearchablesForWebSearchList
                                = new ArrayList<SearchableInfo>();

        final PackageManager pm = mContext.getPackageManager();

@@ -244,127 +224,71 @@ public class Searchables {
            }
        }

        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);
                SearchableInfo searchable = newSearchablesMap.get(component);
                if (searchable == null) {
                    Log.w(LOG_TAG, "did not find component in searchables: " + component);
                } else {
                    newSearchablesForWebSearchList.add(searchable);
                }
            }
        }

        // Find the global search provider
        Intent globalSearchIntent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
        ComponentName globalSearchActivity = globalSearchIntent.resolveActivity(pm);
        SearchableInfo newDefaultSearchable = newSearchablesMap.get(globalSearchActivity);
        // Find the global search activity
        ComponentName newGlobalSearchActivity = findGlobalSearchActivity();

        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(mContext);
        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);
        }
        // Find the web search activity
        ComponentName newWebSearchActivity = findWebSearchActivity(newGlobalSearchActivity);

        // Store a consistent set of new values
        synchronized (this) {
            mSearchablesMap = newSearchablesMap;
            mSearchablesList = newSearchablesList;
            mSearchablesInGlobalSearchList = newSearchablesInGlobalSearchList;
            mSearchablesForWebSearchList = newSearchablesForWebSearchList;
            mDefaultSearchable = newDefaultSearchable;
            mDefaultSearchableForWebSearch = newDefaultSearchableForWebSearch;
            mGlobalSearchActivity = newGlobalSearchActivity;
            mWebSearchActivity = newWebSearchActivity;
        }
    }

    /**
     * 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.
     * 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 static boolean setPreferredActivity(Context context,
            ComponentName component, String action) {
        Log.d(LOG_TAG, "Checking component " + component);
        PackageManager pm = context.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;
    }

    private static ComponentName getPreferredWebSearchActivity(Context context) {
        // Check if we have a preferred web search activity.
        Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
        PackageManager pm = context.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 = context.getResources().getStringArray(
                    com.android.internal.R.array.default_web_search_providers);
            for (String componentName : preferredActivities) {
                ComponentName component = ComponentName.unflattenFromString(componentName);
                if (setPreferredActivity(context, component, Intent.ACTION_WEB_SEARCH)) {
                    return component;
                }
            }
    private ComponentName findGlobalSearchActivity() {
        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 {
            // 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(context, enhancedGoogleSearch,
                        Intent.ACTION_WEB_SEARCH)) {
                    return enhancedGoogleSearch;
                Log.w(LOG_TAG, "Package " + ai.packageName + " wants to handle GLOBAL_SEARCH, "
                        + "but does not have the GLOBAL_SEARCH permission.");
            }
        }
        Log.w(LOG_TAG, "No global search activity found");
        return null;
    }

        if (ri == null) return null;
        return new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name);
    /**
     * Finds the web search activity.
     *
     * Only looks in the package of the global search activity.
     */
    private ComponentName findWebSearchActivity(ComponentName globalSearchActivity) {
        if (globalSearchActivity == null) {
            return null;
        }
        Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
        intent.setPackage(globalSearchActivity.getPackageName());
        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;
            // TODO: do some sanity checks here?
            return new ComponentName(ai.packageName, ai.name);
        }
        Log.w(LOG_TAG, "No web search activity found");
        return null;
    }

    /**
@@ -383,24 +307,16 @@ public class Searchables {
    }

    /**
     * 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.
     * Gets the name of the global search activity.
     */
    public synchronized SearchableInfo getDefaultSearchableForWebSearch() {
        return mDefaultSearchableForWebSearch;
    public synchronized ComponentName getGlobalSearchActivity() {
        return mGlobalSearchActivity;
    }

    /**
     * Sets the default searchable activity for web searches.
     * Gets the name of the web search activity.
     */
    public synchronized void setDefaultWebSearch(ComponentName component) {
        setPreferredActivity(mContext, component, Intent.ACTION_WEB_SEARCH);
        buildSearchableList();
    public synchronized ComponentName getWebSearchActivity() {
        return mWebSearchActivity;
    }
}
Loading