Searchables.java revision f9acde27486bcc6eea1092073f7b47c31749efd6
1875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen/*
2875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen * Copyright (C) 2009 The Android Open Source Project
3875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen *
4875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen * Licensed under the Apache License, Version 2.0 (the "License");
5875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen * you may not use this file except in compliance with the License.
6875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen * You may obtain a copy of the License at
7875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen *
8875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen *      http://www.apache.org/licenses/LICENSE-2.0
9875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen *
10875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen * Unless required by applicable law or agreed to in writing, software
11875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen * distributed under the License is distributed on an "AS IS" BASIS,
12875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen * See the License for the specific language governing permissions and
14875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen * limitations under the License.
15875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen */
16875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen
17875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaenpackage android.server.search;
18875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen
19f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampathimport com.android.internal.app.ResolverActivity;
20f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampathimport com.android.internal.R;
21f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
2274708bbdf8d6f172b08343bdc578a20aa4b39148Bjorn Bringertimport android.app.SearchManager;
23875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaenimport android.content.ComponentName;
24875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaenimport android.content.Context;
25875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaenimport android.content.Intent;
26f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampathimport android.content.IntentFilter;
27875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaenimport android.content.pm.ActivityInfo;
28875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaenimport android.content.pm.PackageManager;
29875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaenimport android.content.pm.ResolveInfo;
30f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampathimport android.content.res.Resources;
31875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaenimport android.os.Bundle;
32cbd8a246f86704fb348247245904a9f114f11280Satish Sampathimport android.util.Log;
33875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen
34875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaenimport java.util.ArrayList;
35875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaenimport java.util.HashMap;
36875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaenimport java.util.List;
37875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen
38875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen/**
39f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath * This class maintains the information about all searchable activities.
40875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen */
41875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaenpublic class Searchables {
42875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen
43cbd8a246f86704fb348247245904a9f114f11280Satish Sampath    private static final String LOG_TAG = "Searchables";
44cbd8a246f86704fb348247245904a9f114f11280Satish Sampath
45875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    // static strings used for XML lookups, etc.
46f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath    // TODO how should these be documented for the developer, in a more structured way than
47875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    // the current long wordy javadoc in SearchManager.java ?
48875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    private static final String MD_LABEL_DEFAULT_SEARCHABLE = "android.app.default_searchable";
49875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    private static final String MD_SEARCHABLE_SYSTEM_SEARCH = "*";
50f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
51875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    private Context mContext;
52f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
53875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    private HashMap<ComponentName, SearchableInfo> mSearchablesMap = null;
54875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    private ArrayList<SearchableInfo> mSearchablesList = null;
556d72e029cb6e5a9cf26aa3314c3dca83614fc91bBjorn Bringert    private ArrayList<SearchableInfo> mSearchablesInGlobalSearchList = null;
56f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath    private ArrayList<SearchableInfo> mSearchablesForWebSearchList = null;
57875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    private SearchableInfo mDefaultSearchable = null;
58f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath    private SearchableInfo mDefaultSearchableForWebSearch = null;
59f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
60875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    /**
61f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     *
62875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * @param context Context to use for looking up activities etc.
63875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     */
64875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    public Searchables (Context context) {
65875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        mContext = context;
66875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    }
67f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
68875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    /**
69875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * Look up, or construct, based on the activity.
70f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     *
71f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     * The activities fall into three cases, based on meta-data found in
72875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * the manifest entry:
73875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * <ol>
74875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * <li>The activity itself implements search.  This is indicated by the
75875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * presence of a "android.app.searchable" meta-data attribute.
76875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * The value is a reference to an XML file containing search information.</li>
77875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * <li>A related activity implements search.  This is indicated by the
78875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * presence of a "android.app.default_searchable" meta-data attribute.
79875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * The value is a string naming the activity implementing search.  In this
80875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * case the factory will "redirect" and return the searchable data.</li>
81875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * <li>No searchability data is provided.  We return null here and other
82875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * code will insert the "default" (e.g. contacts) search.
83f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     *
84875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * TODO: cache the result in the map, and check the map first.
85875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * TODO: it might make sense to implement the searchable reference as
86875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * an application meta-data entry.  This way we don't have to pepper each
87875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * and every activity.
88875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * TODO: can we skip the constructor step if it's a non-searchable?
89f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     * TODO: does it make sense to plug the default into a slot here for
90875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * automatic return?  Probably not, but it's one way to do it.
91875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     *
92f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     * @param activity The name of the current activity, or null if the
93875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * activity does not define any explicit searchable metadata.
94875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     */
95875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    public SearchableInfo getSearchableInfo(ComponentName activity) {
96875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        // Step 1.  Is the result already hashed?  (case 1)
97875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        SearchableInfo result;
98875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        synchronized (this) {
99875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            result = mSearchablesMap.get(activity);
100875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            if (result != null) return result;
101875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        }
102f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
103875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        // Step 2.  See if the current activity references a searchable.
104875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        // Note:  Conceptually, this could be a while(true) loop, but there's
105f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        // no point in implementing reference chaining here and risking a loop.
106875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        // References must point directly to searchable activities.
107f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
108875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        ActivityInfo ai = null;
109875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        try {
110875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            ai = mContext.getPackageManager().
111875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                       getActivityInfo(activity, PackageManager.GET_META_DATA );
112875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            String refActivityName = null;
113f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
114875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            // First look for activity-specific reference
115875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            Bundle md = ai.metaData;
116875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            if (md != null) {
117875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE);
118875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            }
119875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            // If not found, try for app-wide reference
120875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            if (refActivityName == null) {
121875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                md = ai.applicationInfo.metaData;
122875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                if (md != null) {
123875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                    refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE);
124875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                }
125875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            }
126f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
127875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            // Irrespective of source, if a reference was found, follow it.
128875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            if (refActivityName != null)
129875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            {
130f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                // An app or activity can declare that we should simply launch
131875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                // "system default search" if search is invoked.
132875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                if (refActivityName.equals(MD_SEARCHABLE_SYSTEM_SEARCH)) {
133875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                    return getDefaultSearchable();
134875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                }
135875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                String pkg = activity.getPackageName();
136875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                ComponentName referredActivity;
137875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                if (refActivityName.charAt(0) == '.') {
138875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                    referredActivity = new ComponentName(pkg, pkg + refActivityName);
139875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                } else {
140875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                    referredActivity = new ComponentName(pkg, refActivityName);
141875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                }
142875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen
143875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                // Now try the referred activity, and if found, cache
144875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                // it against the original name so we can skip the check
145875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                synchronized (this) {
146875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                    result = mSearchablesMap.get(referredActivity);
147875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                    if (result != null) {
148875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                        mSearchablesMap.put(activity, result);
149875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                        return result;
150875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                    }
151875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                }
152875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            }
153875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        } catch (PackageManager.NameNotFoundException e) {
154875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            // case 3: no metadata
155875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        }
156f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
157875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        // Step 3.  None found. Return null.
158875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        return null;
159f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
160875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    }
161f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
162875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    /**
163875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * Provides the system-default search activity, which you can use
164875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * whenever getSearchableInfo() returns null;
165f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     *
166875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * @return Returns the system-default search activity, null if never defined
167875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     */
168875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    public synchronized SearchableInfo getDefaultSearchable() {
169875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        return mDefaultSearchable;
170875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    }
171f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
172875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    public synchronized boolean isDefaultSearchable(SearchableInfo searchable) {
173875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        return searchable == mDefaultSearchable;
174875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    }
175f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
176875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    /**
177f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     * Builds an entire list (suitable for display) of
178f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     * activities that are searchable, by iterating the entire set of
179f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     * ACTION_SEARCH & ACTION_WEB_SEARCH intents.
180f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     *
181875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * Also clears the hash of all activities -> searches which will
182875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * refill as the user clicks "search".
183f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     *
184875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * This should only be done at startup and again if we know that the
185875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * list has changed.
186f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     *
187875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * TODO: every activity that provides a ACTION_SEARCH intent should
188875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * also provide searchability meta-data.  There are a bunch of checks here
189875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * that, if data is not found, silently skip to the next activity.  This
190875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * won't help a developer trying to figure out why their activity isn't
191875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * showing up in the list, but an exception here is too rough.  I would
192875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * like to find a better notification mechanism.
193f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     *
194875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * TODO: sort the list somehow?  UI choice.
195875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     */
196875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    public void buildSearchableList() {
19774708bbdf8d6f172b08343bdc578a20aa4b39148Bjorn Bringert        // These will become the new values at the end of the method
198f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        HashMap<ComponentName, SearchableInfo> newSearchablesMap
199875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                                = new HashMap<ComponentName, SearchableInfo>();
200875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        ArrayList<SearchableInfo> newSearchablesList
201875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                                = new ArrayList<SearchableInfo>();
2026d72e029cb6e5a9cf26aa3314c3dca83614fc91bBjorn Bringert        ArrayList<SearchableInfo> newSearchablesInGlobalSearchList
2036d72e029cb6e5a9cf26aa3314c3dca83614fc91bBjorn Bringert                                = new ArrayList<SearchableInfo>();
204f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        ArrayList<SearchableInfo> newSearchablesForWebSearchList
205f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                                = new ArrayList<SearchableInfo>();
206875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen
207875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        final PackageManager pm = mContext.getPackageManager();
208f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
209f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        // Use intent resolver to generate list of ACTION_SEARCH & ACTION_WEB_SEARCH receivers.
210f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        List<ResolveInfo> searchList;
211875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        final Intent intent = new Intent(Intent.ACTION_SEARCH);
212f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        searchList = pm.queryIntentActivities(intent, PackageManager.GET_META_DATA);
213f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
214f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        List<ResolveInfo> webSearchInfoList;
215f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        final Intent webSearchIntent = new Intent(Intent.ACTION_WEB_SEARCH);
216f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        webSearchInfoList = pm.queryIntentActivities(webSearchIntent, PackageManager.GET_META_DATA);
217f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
218875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        // analyze each one, generate a Searchables record, and record
219f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        if (searchList != null || webSearchInfoList != null) {
220f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            int search_count = (searchList == null ? 0 : searchList.size());
221f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            int web_search_count = (webSearchInfoList == null ? 0 : webSearchInfoList.size());
222f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            int count = search_count + web_search_count;
223875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            for (int ii = 0; ii < count; ii++) {
224875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                // for each component, try to find metadata
225f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                ResolveInfo info = (ii < search_count)
226f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                        ? searchList.get(ii)
227f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                        : webSearchInfoList.get(ii - search_count);
228875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                ActivityInfo ai = info.activityInfo;
229f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                // Check first to avoid duplicate entries.
230f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                if (newSearchablesMap.get(new ComponentName(ai.packageName, ai.name)) == null) {
231f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                    SearchableInfo searchable = SearchableInfo.getActivityMetaData(mContext, ai);
232f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                    if (searchable != null) {
233f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                        newSearchablesList.add(searchable);
234f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                        newSearchablesMap.put(searchable.getSearchActivity(), searchable);
235f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                        if (searchable.shouldIncludeInGlobalSearch()) {
236f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                            newSearchablesInGlobalSearchList.add(searchable);
237f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                        }
2386d72e029cb6e5a9cf26aa3314c3dca83614fc91bBjorn Bringert                    }
239875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                }
240875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            }
241875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        }
242f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
243f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        if (webSearchInfoList != null) {
244f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            for (int i = 0; i < webSearchInfoList.size(); ++i) {
245f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                ActivityInfo ai = webSearchInfoList.get(i).activityInfo;
246f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                ComponentName component = new ComponentName(ai.packageName, ai.name);
247f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                newSearchablesForWebSearchList.add(newSearchablesMap.get(component));
248f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            }
249f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        }
250f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
25174708bbdf8d6f172b08343bdc578a20aa4b39148Bjorn Bringert        // Find the global search provider
25274708bbdf8d6f172b08343bdc578a20aa4b39148Bjorn Bringert        Intent globalSearchIntent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
25374708bbdf8d6f172b08343bdc578a20aa4b39148Bjorn Bringert        ComponentName globalSearchActivity = globalSearchIntent.resolveActivity(pm);
25474708bbdf8d6f172b08343bdc578a20aa4b39148Bjorn Bringert        SearchableInfo newDefaultSearchable = newSearchablesMap.get(globalSearchActivity);
25574708bbdf8d6f172b08343bdc578a20aa4b39148Bjorn Bringert
256cbd8a246f86704fb348247245904a9f114f11280Satish Sampath        if (newDefaultSearchable == null) {
257cbd8a246f86704fb348247245904a9f114f11280Satish Sampath            Log.w(LOG_TAG, "No searchable info found for new default searchable activity "
258cbd8a246f86704fb348247245904a9f114f11280Satish Sampath                    + globalSearchActivity);
259cbd8a246f86704fb348247245904a9f114f11280Satish Sampath        }
260cbd8a246f86704fb348247245904a9f114f11280Satish Sampath
261f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        // Find the default web search provider.
262f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        ComponentName webSearchActivity = getPreferredWebSearchActivity();
263f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        SearchableInfo newDefaultSearchableForWebSearch = null;
264f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        if (webSearchActivity != null) {
265f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            newDefaultSearchableForWebSearch = newSearchablesMap.get(webSearchActivity);
266f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        }
267f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        if (newDefaultSearchableForWebSearch == null) {
268f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            Log.w(LOG_TAG, "No searchable info found for new default web search activity "
269f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                    + webSearchActivity);
270f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        }
271f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
27274708bbdf8d6f172b08343bdc578a20aa4b39148Bjorn Bringert        // Store a consistent set of new values
273875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        synchronized (this) {
274875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            mSearchablesMap = newSearchablesMap;
2756d72e029cb6e5a9cf26aa3314c3dca83614fc91bBjorn Bringert            mSearchablesList = newSearchablesList;
2766d72e029cb6e5a9cf26aa3314c3dca83614fc91bBjorn Bringert            mSearchablesInGlobalSearchList = newSearchablesInGlobalSearchList;
277f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            mSearchablesForWebSearchList = newSearchablesForWebSearchList;
27874708bbdf8d6f172b08343bdc578a20aa4b39148Bjorn Bringert            mDefaultSearchable = newDefaultSearchable;
279f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            mDefaultSearchableForWebSearch = newDefaultSearchableForWebSearch;
280875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        }
281f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
282f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        // Inform all listeners that the list of searchables has been updated.
283f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        mContext.sendBroadcast(new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED));
284875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    }
285f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
286f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath    /**
287f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     * Checks if the given activity component is present in the system and if so makes it the
288f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     * preferred activity for handling ACTION_WEB_SEARCH.
289f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     * @param component Name of the component to check and set as preferred.
290f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     * @param action Intent action for which this activity is to be set as preferred.
291f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     * @return true if component was detected and set as preferred activity, false if not.
292f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     */
293f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath    private boolean setPreferredActivity(ComponentName component, String action) {
294f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        Log.d(LOG_TAG, "Checking component " + component);
295f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        PackageManager pm = mContext.getPackageManager();
296f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        ActivityInfo ai;
297f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        try {
298f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            ai = pm.getActivityInfo(component, 0);
299f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        } catch (PackageManager.NameNotFoundException e) {
300f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            return false;
301f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        }
302f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
303f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        // The code here to find the value for bestMatch is heavily inspired by the code
304f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        // in ResolverActivity where the preferred activity is set.
305f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        Intent intent = new Intent(action);
306f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        intent.addCategory(Intent.CATEGORY_DEFAULT);
307f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        List<ResolveInfo> webSearchActivities = pm.queryIntentActivities(intent, 0);
308f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        ComponentName set[] = new ComponentName[webSearchActivities.size()];
309f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        int bestMatch = 0;
310f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        for (int i = 0; i < webSearchActivities.size(); ++i) {
311f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            ResolveInfo ri = webSearchActivities.get(i);
312f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            set[i] = new ComponentName(ri.activityInfo.packageName,
313f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                                       ri.activityInfo.name);
314f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            if (ri.match > bestMatch) bestMatch = ri.match;
315f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        }
316f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
317f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        Log.d(LOG_TAG, "Setting preferred web search activity to " + component);
318f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        IntentFilter filter = new IntentFilter(action);
319f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        filter.addCategory(Intent.CATEGORY_DEFAULT);
320f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        pm.replacePreferredActivity(filter, bestMatch, set, component);
321f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        return true;
322f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath    }
323f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
324f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath    public ComponentName getPreferredWebSearchActivity() {
325f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        // Check if we have a preferred web search activity.
326f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
327f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        PackageManager pm = mContext.getPackageManager();
328f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
329f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
330f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        if (ri == null || ri.activityInfo.name.equals(ResolverActivity.class.getName())) {
331f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            Log.d(LOG_TAG, "No preferred activity set for action web search.");
332f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
333f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            // The components in the providers array are checked in the order of declaration so the
334f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            // first one has the highest priority. If the component exists in the system it is set
335f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            // as the preferred activity to handle intent action web search.
336f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            String[] preferredActivities = mContext.getResources().getStringArray(
337f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                    com.android.internal.R.array.default_web_search_providers);
338f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            for (String componentName : preferredActivities) {
339f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                ComponentName component = ComponentName.unflattenFromString(componentName);
340f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                if (setPreferredActivity(component, Intent.ACTION_WEB_SEARCH)) {
341f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                    return component;
342f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                }
343f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            }
344f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        }
345f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
346f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        if (ri == null) return null;
347f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        return new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name);
348f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath    }
349f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
350875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    /**
351875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * Returns the list of searchable activities.
352875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     */
353875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    public synchronized ArrayList<SearchableInfo> getSearchablesList() {
354875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        ArrayList<SearchableInfo> result = new ArrayList<SearchableInfo>(mSearchablesList);
355875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        return result;
356875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    }
357f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
3586d72e029cb6e5a9cf26aa3314c3dca83614fc91bBjorn Bringert    /**
3596d72e029cb6e5a9cf26aa3314c3dca83614fc91bBjorn Bringert     * Returns a list of the searchable activities that can be included in global search.
3606d72e029cb6e5a9cf26aa3314c3dca83614fc91bBjorn Bringert     */
3616d72e029cb6e5a9cf26aa3314c3dca83614fc91bBjorn Bringert    public synchronized ArrayList<SearchableInfo> getSearchablesInGlobalSearchList() {
3626d72e029cb6e5a9cf26aa3314c3dca83614fc91bBjorn Bringert        return new ArrayList<SearchableInfo>(mSearchablesInGlobalSearchList);
3636d72e029cb6e5a9cf26aa3314c3dca83614fc91bBjorn Bringert    }
364f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
365f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath    /**
366f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     * Returns a list of the searchable activities that handle web searches.
367f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     */
368f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath    public synchronized ArrayList<SearchableInfo> getSearchablesForWebSearchList() {
369f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        return new ArrayList<SearchableInfo>(mSearchablesForWebSearchList);
370f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath    }
371f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
372f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath    /**
373f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     * Returns the default searchable activity for web searches.
374f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     */
375f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath    public synchronized SearchableInfo getDefaultSearchableForWebSearch() {
376f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        return mDefaultSearchableForWebSearch;
377f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath    }
378f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
379f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath    /**
380f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     * Sets the default searchable activity for web searches.
381f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     */
382f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath    public synchronized void setDefaultWebSearch(ComponentName component) {
383f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        setPreferredActivity(component, Intent.ACTION_WEB_SEARCH);
384f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        buildSearchableList();
385f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath    }
386875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen}
387