All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.com.android.server.search.Searchables Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 15-robolectric-12650502
Show newest version
/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.search;

import android.app.AppGlobals;
import android.app.SearchManager;
import android.app.SearchableInfo;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;

import com.android.server.LocalServices;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;

/**
 * This class maintains the information about all searchable activities.
 * This is a hidden class.
 */
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
    // 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 mSearchablesMap = null;
    private ArrayList mSearchablesList = null;
    private ArrayList mSearchablesInGlobalSearchList = null;
    // Contains all installed activities that handle the global search
    // intent.
    private List mGlobalSearchActivities;
    private ComponentName mCurrentGlobalSearchActivity = null;
    private ComponentName mWebSearchActivity = 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";

    // Cache the package manager instance
    final private IPackageManager mPm;
    // User for which this Searchables caches information
    private int mUserId;

    /**
     *
     * @param context Context to use for looking up activities etc.
     */
    public Searchables (Context context, int userId) {
        mContext = context;
        mUserId = userId;
        mPm = AppGlobals.getPackageManager();
    }

    /**
     * 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. *
  3. 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.
  4. *
  5. 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. */ public SearchableInfo getSearchableInfo(ComponentName activity) { // Step 1. Is the result already hashed? (case 1) SearchableInfo result; synchronized (this) { result = mSearchablesMap.get(activity); if (result != null) { final PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class); if (pm.canAccessComponent(Binder.getCallingUid(), result.getSearchActivity(), UserHandle.getCallingUserId())) { return result; } return null; } } // 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); } } if (result != null) { final PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class); if (pm.canAccessComponent(Binder.getCallingUid(), result.getSearchActivity(), UserHandle.getCallingUserId())) { return result; } return null; } } // Step 3. None found. Return null. return null; } /** * 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 updateSearchableList() { // These will become the new values at the end of the method HashMap newSearchablesMap = new HashMap(); ArrayList newSearchablesList = new ArrayList(); ArrayList newSearchablesInGlobalSearchList = new ArrayList(); // Use intent resolver to generate list of ACTION_SEARCH & ACTION_WEB_SEARCH receivers. List searchList; final Intent intent = new Intent(Intent.ACTION_SEARCH); final long ident = Binder.clearCallingIdentity(); try { searchList = queryIntentActivities(intent, PackageManager.GET_META_DATA | PackageManager.MATCH_DEBUG_TRIAGED_MISSING); List webSearchInfoList; final Intent webSearchIntent = new Intent(Intent.ACTION_WEB_SEARCH); webSearchInfoList = queryIntentActivities(webSearchIntent, PackageManager.GET_META_DATA | PackageManager.MATCH_DEBUG_TRIAGED_MISSING); // 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 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); } } /** * 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 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); List activities = queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY | PackageManager.MATCH_DEBUG_TRIAGED_MISSING); 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. */ private ComponentName findGlobalSearchActivity(List 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); List activities = queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); if (activities != null && !activities.isEmpty()) { return true; } return false; } private static final Comparator GLOBAL_SEARCH_RANKER = new Comparator() { @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 { // 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 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() { final ContentResolver cr = mContext.getContentResolver(); return Settings.Secure.getStringForUser(cr, Settings.Secure.SEARCH_GLOBAL_SEARCH_ACTIVITY, cr.getUserId()); } /** * 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()); List activities = queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); if (activities != null && !activities.isEmpty()) { ActivityInfo ai = activities.get(0).activityInfo; // TODO: do some validity checks here? return new ComponentName(ai.packageName, ai.name); } Log.w(LOG_TAG, "No web search activity found"); return null; } private List queryIntentActivities(Intent intent, int flags) { List activities = null; try { activities = mPm.queryIntentActivities(intent, intent.resolveTypeIfNeeded(mContext.getContentResolver()), flags | PackageManager.MATCH_INSTANT, mUserId).getList(); } catch (RemoteException re) { // Local call } return activities; } /** * Returns the list of searchable activities. */ public synchronized ArrayList getSearchablesList() { return createFilterdSearchableInfoList(mSearchablesList); } /** * Returns a list of the searchable activities that can be included in global search. */ public synchronized ArrayList getSearchablesInGlobalSearchList() { return createFilterdSearchableInfoList(mSearchablesInGlobalSearchList); } /** * Returns a list of activities that handle the global search intent. */ public synchronized ArrayList getGlobalSearchActivities() { return createFilterdResolveInfoList(mGlobalSearchActivities); } private ArrayList createFilterdSearchableInfoList(List list) { if (list == null) { return null; } final ArrayList resultList = new ArrayList<>(list.size()); final PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class); final int callingUid = Binder.getCallingUid(); final int callingUserId = UserHandle.getCallingUserId(); for (SearchableInfo info : list) { if (pm.canAccessComponent(callingUid, info.getSearchActivity(), callingUserId)) { resultList.add(info); } } return resultList; } private ArrayList createFilterdResolveInfoList(List list) { if (list == null) { return null; } final ArrayList resultList = new ArrayList<>(list.size()); final PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class); final int callingUid = Binder.getCallingUid(); final int callingUserId = UserHandle.getCallingUserId(); for (ResolveInfo info : list) { if (pm.canAccessComponent( callingUid, info.activityInfo.getComponentName(), callingUserId)) { resultList.add(info); } } return resultList; } /** * Gets the name of the global search activity. */ public synchronized ComponentName getGlobalSearchActivity() { final PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class); final int callingUid = Binder.getCallingUid(); final int callingUserId = UserHandle.getCallingUserId(); if (mCurrentGlobalSearchActivity != null && pm.canAccessComponent(callingUid, mCurrentGlobalSearchActivity, callingUserId)) { return mCurrentGlobalSearchActivity; } return null; } /** * Gets the name of the web search activity. */ public synchronized ComponentName getWebSearchActivity() { final PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class); final int callingUid = Binder.getCallingUid(); final int callingUserId = UserHandle.getCallingUserId(); if (mWebSearchActivity != null && pm.canAccessComponent(callingUid, mWebSearchActivity, callingUserId)) { return mWebSearchActivity; } return null; } void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("Searchable authorities:"); synchronized (this) { if (mSearchablesList != null) { for (SearchableInfo info: mSearchablesList) { pw.print(" "); pw.println(info.getSuggestAuthority()); } } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy