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

Commit 962402f4 authored by Narayan Kamath's avatar Narayan Kamath Committed by Android (Google) Code Review
Browse files

Merge "Make the system global search provider a user setting."

parents 631c57c4 ee69ff4e
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -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();
}
+27 −0
Original line number Diff line number Diff line
@@ -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.
     *
+13 −0
Original line number Diff line number Diff line
@@ -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
+34 −0
Original line number Diff line number Diff line
@@ -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.
     */
+124 −21
Original line number Diff line number Diff line
@@ -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;
        }
    }
    /**
     * Returns a sorted list of installed search providers as per
     * the following heuristics:
     *
     * (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 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);

        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.
     *
     * 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 ComponentName findGlobalSearchActivity() {
    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);
        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);

        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 {
                Log.w(LOG_TAG, "Package " + ai.packageName + " wants to handle GLOBAL_SEARCH, "
                        + "but does not have the GLOBAL_SEARCH permission.");
                // 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;
    }

    /**