FileDocCategorySizeDatePackage
Searchables.javaAPI DocAndroid 5.1 API18645Thu Mar 12 22:22:42 GMT 2015com.android.server.search

Searchables

public class Searchables extends Object
This class maintains the information about all searchable activities. This is a hidden class.

Fields Summary
private static final String
LOG_TAG
private static final String
MD_LABEL_DEFAULT_SEARCHABLE
private static final String
MD_SEARCHABLE_SYSTEM_SEARCH
private android.content.Context
mContext
private HashMap
mSearchablesMap
private ArrayList
mSearchablesList
private ArrayList
mSearchablesInGlobalSearchList
private List
mGlobalSearchActivities
private android.content.ComponentName
mCurrentGlobalSearchActivity
private android.content.ComponentName
mWebSearchActivity
public static String
GOOGLE_SEARCH_COMPONENT_NAME
public static String
ENHANCED_GOOGLE_SEARCH_COMPONENT_NAME
private final android.content.pm.IPackageManager
mPm
private int
mUserId
private static final Comparator
GLOBAL_SEARCH_RANKER
Constructors Summary
public Searchables(android.content.Context context, int userId)

param
context Context to use for looking up activities etc.


                   
          
        mContext = context;
        mUserId = userId;
        mPm = AppGlobals.getPackageManager();
    
Methods Summary
public voidbuildSearchableList()
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.

        // These will become the new values at the end of the method
        HashMap<ComponentName, SearchableInfo> newSearchablesMap
                                = new HashMap<ComponentName, SearchableInfo>();
        ArrayList<SearchableInfo> newSearchablesList
                                = new ArrayList<SearchableInfo>();
        ArrayList<SearchableInfo> newSearchablesInGlobalSearchList
                                = new ArrayList<SearchableInfo>();

        // Use intent resolver to generate list of ACTION_SEARCH & ACTION_WEB_SEARCH receivers.
        List<ResolveInfo> searchList;
        final Intent intent = new Intent(Intent.ACTION_SEARCH);

        long ident = Binder.clearCallingIdentity();
        try {
            searchList = queryIntentActivities(intent, PackageManager.GET_META_DATA);

            List<ResolveInfo> webSearchInfoList;
            final Intent webSearchIntent = new Intent(Intent.ACTION_WEB_SEARCH);
            webSearchInfoList = queryIntentActivities(webSearchIntent, PackageManager.GET_META_DATA);

            // analyze each one, generate a Searchables record, and record
            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 = (ii < search_count)
                            ? searchList.get(ii)
                            : webSearchInfoList.get(ii - search_count);
                    ActivityInfo ai = info.activityInfo;
                    // Check first to avoid duplicate entries.
                    if (newSearchablesMap.get(new ComponentName(ai.packageName, ai.name)) == null) {
                        SearchableInfo searchable = SearchableInfo.getActivityMetaData(mContext, ai,
                                mUserId);
                        if (searchable != null) {
                            newSearchablesList.add(searchable);
                            newSearchablesMap.put(searchable.getSearchActivity(), searchable);
                            if (searchable.shouldIncludeInGlobalSearch()) {
                                newSearchablesInGlobalSearchList.add(searchable);
                            }
                        }
                    }
                }
            }

            List<ResolveInfo> newGlobalSearchActivities = findGlobalSearchActivities();

            // Find the global search activity
            ComponentName newGlobalSearchActivity = findGlobalSearchActivity(
                    newGlobalSearchActivities);

            // Find the web search activity
            ComponentName newWebSearchActivity = findWebSearchActivity(newGlobalSearchActivity);

            // Store a consistent set of new values
            synchronized (this) {
                mSearchablesMap = newSearchablesMap;
                mSearchablesList = newSearchablesList;
                mSearchablesInGlobalSearchList = newSearchablesInGlobalSearchList;
                mGlobalSearchActivities = newGlobalSearchActivities;
                mCurrentGlobalSearchActivity = newGlobalSearchActivity;
                mWebSearchActivity = newWebSearchActivity;
            }
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    
voiddump(java.io.FileDescriptor fd, java.io.PrintWriter pw, java.lang.String[] args)

        pw.println("Searchable authorities:");
        synchronized (this) {
            if (mSearchablesList != null) {
                for (SearchableInfo info: mSearchablesList) {
                    pw.print("  "); pw.println(info.getSuggestAuthority());
                }
            }
        }
    
private java.util.ListfindGlobalSearchActivities()
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.

        // 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);
        List<ResolveInfo> activities =
                    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;
    
private android.content.ComponentNamefindGlobalSearchActivity(java.util.List installed)
Finds the global search activity.

        // 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);
    
private android.content.ComponentNamefindWebSearchActivity(android.content.ComponentName globalSearchActivity)
Finds the web search activity. Only looks in the package of the global search activity.

        if (globalSearchActivity == null) {
            return null;
        }
        Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
        intent.setPackage(globalSearchActivity.getPackageName());
        List<ResolveInfo> activities =
                queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);

        if (activities != null && !activities.isEmpty()) {
            ActivityInfo ai = activities.get(0).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;
    
private android.content.ComponentNamegetDefaultGlobalSearchProvider(java.util.List providerList)
Returns the highest ranked search provider as per the ranking defined in {@link #getGlobalSearchActivities()}.

        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;
    
public synchronized java.util.ArrayListgetGlobalSearchActivities()
Returns a list of activities that handle the global search intent.

        return new ArrayList<ResolveInfo>(mGlobalSearchActivities);
    
public synchronized android.content.ComponentNamegetGlobalSearchActivity()
Gets the name of the global search activity.

        return mCurrentGlobalSearchActivity;
    
private java.lang.StringgetGlobalSearchProviderSetting()

        return Settings.Secure.getString(mContext.getContentResolver(),
                Settings.Secure.SEARCH_GLOBAL_SEARCH_ACTIVITY);
    
public android.app.SearchableInfogetSearchableInfo(android.content.ComponentName activity)
Look up, or construct, based on the activity. The activities fall into three cases, based on meta-data found in the manifest entry:
  1. The activity itself implements search. This is indicated by the presence of a "android.app.searchable" meta-data attribute. The value is a reference to an XML file containing search information.
  2. A related activity implements search. This is indicated by the presence of a "android.app.default_searchable" meta-data attribute. The value is a string naming the activity implementing search. In this case the factory will "redirect" and return the searchable data.
  3. 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 automatic return? Probably not, but it's one way to do it.

    param
    activity The name of the current activity, or null if the activity does not define any explicit searchable metadata.

            // Step 1.  Is the result already hashed?  (case 1)
            SearchableInfo result;
            synchronized (this) {
                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.
            // References must point directly to searchable activities.
    
            ActivityInfo ai = null;
            try {
                ai = mPm.getActivityInfo(activity, PackageManager.GET_META_DATA, mUserId);
            } catch (RemoteException re) {
                Log.e(LOG_TAG, "Error getting activity info " + re);
                return null;
            }
            String refActivityName = null;
    
            // First look for activity-specific reference
            Bundle md = ai.metaData;
            if (md != null) {
                refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE);
            }
            // If not found, try for app-wide reference
            if (refActivityName == null) {
                md = ai.applicationInfo.metaData;
                if (md != null) {
                    refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE);
                }
            }
    
            // Irrespective of source, if a reference was found, follow it.
            if (refActivityName != null)
            {
                // This value is deprecated, return null
                if (refActivityName.equals(MD_SEARCHABLE_SYSTEM_SEARCH)) {
                    return null;
                }
                String pkg = activity.getPackageName();
                ComponentName referredActivity;
                if (refActivityName.charAt(0) == '.") {
                    referredActivity = new ComponentName(pkg, pkg + refActivityName);
                } else {
                    referredActivity = new ComponentName(pkg, refActivityName);
                }
    
                // Now try the referred activity, and if found, cache
                // it against the original name so we can skip the check
                synchronized (this) {
                    result = mSearchablesMap.get(referredActivity);
                    if (result != null) {
                        mSearchablesMap.put(activity, result);
                        return result;
                    }
                }
            }
    
            // Step 3.  None found. Return null.
            return null;
    
        
public synchronized java.util.ArrayListgetSearchablesInGlobalSearchList()
Returns a list of the searchable activities that can be included in global search.

        return new ArrayList<SearchableInfo>(mSearchablesInGlobalSearchList);
    
public synchronized java.util.ArrayListgetSearchablesList()
Returns the list of searchable activities.

        ArrayList<SearchableInfo> result = new ArrayList<SearchableInfo>(mSearchablesList);
        return result;
    
public synchronized android.content.ComponentNamegetWebSearchActivity()
Gets the name of the web search activity.

        return mWebSearchActivity;
    
private booleanisInstalled(android.content.ComponentName globalSearch)
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.

        Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
        intent.setComponent(globalSearch);

        List<ResolveInfo> activities = queryIntentActivities(intent,
                PackageManager.MATCH_DEFAULT_ONLY);
        if (activities != null && !activities.isEmpty()) {
            return true;
        }

        return false;
    
private static final booleanisSystemApp(android.content.pm.ResolveInfo res)

return
true iff. the resolve info corresponds to a system application.


                    
          
        return (res.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
    
private java.util.ListqueryIntentActivities(android.content.Intent intent, int flags)

        List<ResolveInfo> activities = null;
        try {
            activities =
                    mPm.queryIntentActivities(intent,
                    intent.resolveTypeIfNeeded(mContext.getContentResolver()),
                    flags, mUserId);
        } catch (RemoteException re) {
            // Local call
        }
        return activities;