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 Sampath
2174708bbdf8d6f172b08343bdc578a20aa4b39148Bjorn Bringertimport android.app.SearchManager;
22875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaenimport android.content.ComponentName;
23875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaenimport android.content.Context;
24875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaenimport android.content.Intent;
25f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampathimport android.content.IntentFilter;
26875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaenimport android.content.pm.ActivityInfo;
27875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaenimport android.content.pm.PackageManager;
28875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaenimport android.content.pm.ResolveInfo;
29875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaenimport android.os.Bundle;
30cbd8a246f86704fb348247245904a9f114f11280Satish Sampathimport android.util.Log;
31875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen
32875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaenimport java.util.ArrayList;
33875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaenimport java.util.HashMap;
34875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaenimport java.util.List;
35875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen
36875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen/**
37f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath * This class maintains the information about all searchable activities.
38875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen */
39875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaenpublic class Searchables {
40875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen
41cbd8a246f86704fb348247245904a9f114f11280Satish Sampath    private static final String LOG_TAG = "Searchables";
42cbd8a246f86704fb348247245904a9f114f11280Satish Sampath
43875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    // static strings used for XML lookups, etc.
44f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath    // TODO how should these be documented for the developer, in a more structured way than
45875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    // the current long wordy javadoc in SearchManager.java ?
46875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    private static final String MD_LABEL_DEFAULT_SEARCHABLE = "android.app.default_searchable";
47875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    private static final String MD_SEARCHABLE_SYSTEM_SEARCH = "*";
48f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
49875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    private Context mContext;
50f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
51875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    private HashMap<ComponentName, SearchableInfo> mSearchablesMap = null;
52875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    private ArrayList<SearchableInfo> mSearchablesList = null;
536d72e029cb6e5a9cf26aa3314c3dca83614fc91bBjorn Bringert    private ArrayList<SearchableInfo> mSearchablesInGlobalSearchList = null;
54f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath    private ArrayList<SearchableInfo> mSearchablesForWebSearchList = null;
55875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    private SearchableInfo mDefaultSearchable = null;
56f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath    private SearchableInfo mDefaultSearchableForWebSearch = null;
57f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
5841282a35568b51270440450c46bb31aa00e52caaSatish Sampath    public static String GOOGLE_SEARCH_COMPONENT_NAME =
5941282a35568b51270440450c46bb31aa00e52caaSatish Sampath            "com.android.googlesearch/.GoogleSearch";
6041282a35568b51270440450c46bb31aa00e52caaSatish Sampath    public static String ENHANCED_GOOGLE_SEARCH_COMPONENT_NAME =
6141282a35568b51270440450c46bb31aa00e52caaSatish Sampath            "com.google.android.providers.enhancedgooglesearch/.Launcher";
6241282a35568b51270440450c46bb31aa00e52caaSatish Sampath
63875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    /**
64f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     *
65875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * @param context Context to use for looking up activities etc.
66875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     */
67875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    public Searchables (Context context) {
68875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        mContext = context;
69875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    }
70f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
71875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    /**
72875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * Look up, or construct, based on the activity.
73f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     *
74f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     * The activities fall into three cases, based on meta-data found in
75875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * the manifest entry:
76875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * <ol>
77875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * <li>The activity itself implements search.  This is indicated by the
78875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * presence of a "android.app.searchable" meta-data attribute.
79875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * The value is a reference to an XML file containing search information.</li>
80875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * <li>A related activity implements search.  This is indicated by the
81875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * presence of a "android.app.default_searchable" meta-data attribute.
82875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * The value is a string naming the activity implementing search.  In this
83875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * case the factory will "redirect" and return the searchable data.</li>
84875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * <li>No searchability data is provided.  We return null here and other
85875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * code will insert the "default" (e.g. contacts) search.
86f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     *
87875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * TODO: cache the result in the map, and check the map first.
88875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * TODO: it might make sense to implement the searchable reference as
89875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * an application meta-data entry.  This way we don't have to pepper each
90875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * and every activity.
91875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * TODO: can we skip the constructor step if it's a non-searchable?
92f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     * TODO: does it make sense to plug the default into a slot here for
93875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * automatic return?  Probably not, but it's one way to do it.
94875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     *
95f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     * @param activity The name of the current activity, or null if the
96875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * activity does not define any explicit searchable metadata.
97875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     */
98875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    public SearchableInfo getSearchableInfo(ComponentName activity) {
99875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        // Step 1.  Is the result already hashed?  (case 1)
100875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        SearchableInfo result;
101875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        synchronized (this) {
102875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            result = mSearchablesMap.get(activity);
103875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            if (result != null) return result;
104875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        }
105f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
106875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        // Step 2.  See if the current activity references a searchable.
107875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        // Note:  Conceptually, this could be a while(true) loop, but there's
108f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        // no point in implementing reference chaining here and risking a loop.
109875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        // References must point directly to searchable activities.
110f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
111875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        ActivityInfo ai = null;
112875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        try {
113875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            ai = mContext.getPackageManager().
114875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                       getActivityInfo(activity, PackageManager.GET_META_DATA );
115875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            String refActivityName = null;
116f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
117875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            // First look for activity-specific reference
118875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            Bundle md = ai.metaData;
119875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            if (md != null) {
120875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE);
121875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            }
122875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            // If not found, try for app-wide reference
123875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            if (refActivityName == null) {
124875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                md = ai.applicationInfo.metaData;
125875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                if (md != null) {
126875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                    refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE);
127875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                }
128875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            }
129f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
130875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            // Irrespective of source, if a reference was found, follow it.
131875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            if (refActivityName != null)
132875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            {
133f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                // An app or activity can declare that we should simply launch
134875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                // "system default search" if search is invoked.
135875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                if (refActivityName.equals(MD_SEARCHABLE_SYSTEM_SEARCH)) {
136875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                    return getDefaultSearchable();
137875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                }
138875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                String pkg = activity.getPackageName();
139875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                ComponentName referredActivity;
140875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                if (refActivityName.charAt(0) == '.') {
141875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                    referredActivity = new ComponentName(pkg, pkg + refActivityName);
142875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                } else {
143875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                    referredActivity = new ComponentName(pkg, refActivityName);
144875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                }
145875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen
146875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                // Now try the referred activity, and if found, cache
147875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                // it against the original name so we can skip the check
148875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                synchronized (this) {
149875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                    result = mSearchablesMap.get(referredActivity);
150875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                    if (result != null) {
151875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                        mSearchablesMap.put(activity, result);
152875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                        return result;
153875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                    }
154875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                }
155875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            }
156875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        } catch (PackageManager.NameNotFoundException e) {
157875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            // case 3: no metadata
158875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        }
159f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
160875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        // Step 3.  None found. Return null.
161875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        return null;
162f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
163875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    }
164f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
165875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    /**
166875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * Provides the system-default search activity, which you can use
167875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * whenever getSearchableInfo() returns null;
168f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     *
169875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * @return Returns the system-default search activity, null if never defined
170875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     */
171875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    public synchronized SearchableInfo getDefaultSearchable() {
172875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        return mDefaultSearchable;
173875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    }
174f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
175875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    public synchronized boolean isDefaultSearchable(SearchableInfo searchable) {
176875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        return searchable == mDefaultSearchable;
177875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    }
178f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
179875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    /**
180f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     * Builds an entire list (suitable for display) of
181f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     * activities that are searchable, by iterating the entire set of
182f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     * ACTION_SEARCH & ACTION_WEB_SEARCH intents.
183f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     *
184875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * Also clears the hash of all activities -> searches which will
185875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * refill as the user clicks "search".
186f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     *
187875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * This should only be done at startup and again if we know that the
188875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * list has changed.
189f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     *
190875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * TODO: every activity that provides a ACTION_SEARCH intent should
191875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * also provide searchability meta-data.  There are a bunch of checks here
192875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * that, if data is not found, silently skip to the next activity.  This
193875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * won't help a developer trying to figure out why their activity isn't
194875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * showing up in the list, but an exception here is too rough.  I would
195875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * like to find a better notification mechanism.
196f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     *
197875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * TODO: sort the list somehow?  UI choice.
198875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     */
199875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    public void buildSearchableList() {
20074708bbdf8d6f172b08343bdc578a20aa4b39148Bjorn Bringert        // These will become the new values at the end of the method
201f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        HashMap<ComponentName, SearchableInfo> newSearchablesMap
202875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                                = new HashMap<ComponentName, SearchableInfo>();
203875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        ArrayList<SearchableInfo> newSearchablesList
204875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                                = new ArrayList<SearchableInfo>();
2056d72e029cb6e5a9cf26aa3314c3dca83614fc91bBjorn Bringert        ArrayList<SearchableInfo> newSearchablesInGlobalSearchList
2066d72e029cb6e5a9cf26aa3314c3dca83614fc91bBjorn Bringert                                = new ArrayList<SearchableInfo>();
207f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        ArrayList<SearchableInfo> newSearchablesForWebSearchList
208f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                                = new ArrayList<SearchableInfo>();
209875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen
210875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        final PackageManager pm = mContext.getPackageManager();
211f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
212f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        // Use intent resolver to generate list of ACTION_SEARCH & ACTION_WEB_SEARCH receivers.
213f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        List<ResolveInfo> searchList;
214875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        final Intent intent = new Intent(Intent.ACTION_SEARCH);
215f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        searchList = pm.queryIntentActivities(intent, PackageManager.GET_META_DATA);
216f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
217f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        List<ResolveInfo> webSearchInfoList;
218f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        final Intent webSearchIntent = new Intent(Intent.ACTION_WEB_SEARCH);
219f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        webSearchInfoList = pm.queryIntentActivities(webSearchIntent, PackageManager.GET_META_DATA);
220f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
221875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        // analyze each one, generate a Searchables record, and record
222f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        if (searchList != null || webSearchInfoList != null) {
223f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            int search_count = (searchList == null ? 0 : searchList.size());
224f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            int web_search_count = (webSearchInfoList == null ? 0 : webSearchInfoList.size());
225f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            int count = search_count + web_search_count;
226875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            for (int ii = 0; ii < count; ii++) {
227875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                // for each component, try to find metadata
228f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                ResolveInfo info = (ii < search_count)
229f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                        ? searchList.get(ii)
230f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                        : webSearchInfoList.get(ii - search_count);
231875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                ActivityInfo ai = info.activityInfo;
232f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                // Check first to avoid duplicate entries.
233590f63433ce786722d263c7e913a88d3101e5cbcKarl Rosaen                if (newSearchablesMap.get(new ComponentName(ai.packageName, ai.name)) == null) {
234590f63433ce786722d263c7e913a88d3101e5cbcKarl Rosaen                    SearchableInfo searchable = SearchableInfo.getActivityMetaData(mContext, ai);
235590f63433ce786722d263c7e913a88d3101e5cbcKarl Rosaen                    if (searchable != null) {
236590f63433ce786722d263c7e913a88d3101e5cbcKarl Rosaen                        newSearchablesList.add(searchable);
237590f63433ce786722d263c7e913a88d3101e5cbcKarl Rosaen                        newSearchablesMap.put(searchable.getSearchActivity(), searchable);
238590f63433ce786722d263c7e913a88d3101e5cbcKarl Rosaen                        if (searchable.shouldIncludeInGlobalSearch()) {
239590f63433ce786722d263c7e913a88d3101e5cbcKarl Rosaen                            newSearchablesInGlobalSearchList.add(searchable);
240590f63433ce786722d263c7e913a88d3101e5cbcKarl Rosaen                        }
2416d72e029cb6e5a9cf26aa3314c3dca83614fc91bBjorn Bringert                    }
242875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen                }
243875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            }
244875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        }
245f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
246f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        if (webSearchInfoList != null) {
247f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            for (int i = 0; i < webSearchInfoList.size(); ++i) {
248f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                ActivityInfo ai = webSearchInfoList.get(i).activityInfo;
249f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                ComponentName component = new ComponentName(ai.packageName, ai.name);
250f4422ce8293bdb4b6d9f091d38ab588d0bb2e4e4Mike LeBeau                SearchableInfo searchable = newSearchablesMap.get(component);
251f4422ce8293bdb4b6d9f091d38ab588d0bb2e4e4Mike LeBeau                if (searchable == null) {
252f4422ce8293bdb4b6d9f091d38ab588d0bb2e4e4Mike LeBeau                    Log.w(LOG_TAG, "did not find component in searchables: " + component);
253f4422ce8293bdb4b6d9f091d38ab588d0bb2e4e4Mike LeBeau                } else {
254f4422ce8293bdb4b6d9f091d38ab588d0bb2e4e4Mike LeBeau                    newSearchablesForWebSearchList.add(searchable);
255f4422ce8293bdb4b6d9f091d38ab588d0bb2e4e4Mike LeBeau                }
256f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            }
257f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        }
258f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
25974708bbdf8d6f172b08343bdc578a20aa4b39148Bjorn Bringert        // Find the global search provider
26074708bbdf8d6f172b08343bdc578a20aa4b39148Bjorn Bringert        Intent globalSearchIntent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
26174708bbdf8d6f172b08343bdc578a20aa4b39148Bjorn Bringert        ComponentName globalSearchActivity = globalSearchIntent.resolveActivity(pm);
26274708bbdf8d6f172b08343bdc578a20aa4b39148Bjorn Bringert        SearchableInfo newDefaultSearchable = newSearchablesMap.get(globalSearchActivity);
26374708bbdf8d6f172b08343bdc578a20aa4b39148Bjorn Bringert
264cbd8a246f86704fb348247245904a9f114f11280Satish Sampath        if (newDefaultSearchable == null) {
265cbd8a246f86704fb348247245904a9f114f11280Satish Sampath            Log.w(LOG_TAG, "No searchable info found for new default searchable activity "
266cbd8a246f86704fb348247245904a9f114f11280Satish Sampath                    + globalSearchActivity);
267cbd8a246f86704fb348247245904a9f114f11280Satish Sampath        }
268cbd8a246f86704fb348247245904a9f114f11280Satish Sampath
269f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        // Find the default web search provider.
270444c727e0eecf83e9d0b9c4e7af5cbf5fc4135f8Bjorn Bringert        ComponentName webSearchActivity = getPreferredWebSearchActivity(mContext);
271f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        SearchableInfo newDefaultSearchableForWebSearch = null;
272f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        if (webSearchActivity != null) {
273f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            newDefaultSearchableForWebSearch = newSearchablesMap.get(webSearchActivity);
274f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        }
275f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        if (newDefaultSearchableForWebSearch == null) {
276f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            Log.w(LOG_TAG, "No searchable info found for new default web search activity "
277f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                    + webSearchActivity);
278f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        }
279f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
28074708bbdf8d6f172b08343bdc578a20aa4b39148Bjorn Bringert        // Store a consistent set of new values
281875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        synchronized (this) {
282875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen            mSearchablesMap = newSearchablesMap;
2836d72e029cb6e5a9cf26aa3314c3dca83614fc91bBjorn Bringert            mSearchablesList = newSearchablesList;
2846d72e029cb6e5a9cf26aa3314c3dca83614fc91bBjorn Bringert            mSearchablesInGlobalSearchList = newSearchablesInGlobalSearchList;
285f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            mSearchablesForWebSearchList = newSearchablesForWebSearchList;
28674708bbdf8d6f172b08343bdc578a20aa4b39148Bjorn Bringert            mDefaultSearchable = newDefaultSearchable;
287f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            mDefaultSearchableForWebSearch = newDefaultSearchableForWebSearch;
288875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        }
289875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    }
290f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
291f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath    /**
292f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     * Checks if the given activity component is present in the system and if so makes it the
293f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     * preferred activity for handling ACTION_WEB_SEARCH.
294f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     * @param component Name of the component to check and set as preferred.
295f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     * @param action Intent action for which this activity is to be set as preferred.
296f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     * @return true if component was detected and set as preferred activity, false if not.
297f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     */
298444c727e0eecf83e9d0b9c4e7af5cbf5fc4135f8Bjorn Bringert    private static boolean setPreferredActivity(Context context,
299444c727e0eecf83e9d0b9c4e7af5cbf5fc4135f8Bjorn Bringert            ComponentName component, String action) {
300f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        Log.d(LOG_TAG, "Checking component " + component);
301444c727e0eecf83e9d0b9c4e7af5cbf5fc4135f8Bjorn Bringert        PackageManager pm = context.getPackageManager();
302f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        ActivityInfo ai;
303f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        try {
304f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            ai = pm.getActivityInfo(component, 0);
305f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        } catch (PackageManager.NameNotFoundException e) {
306f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            return false;
307f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        }
308f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
309f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        // The code here to find the value for bestMatch is heavily inspired by the code
310f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        // in ResolverActivity where the preferred activity is set.
311f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        Intent intent = new Intent(action);
312f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        intent.addCategory(Intent.CATEGORY_DEFAULT);
313f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        List<ResolveInfo> webSearchActivities = pm.queryIntentActivities(intent, 0);
314f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        ComponentName set[] = new ComponentName[webSearchActivities.size()];
315f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        int bestMatch = 0;
316f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        for (int i = 0; i < webSearchActivities.size(); ++i) {
317f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            ResolveInfo ri = webSearchActivities.get(i);
318f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            set[i] = new ComponentName(ri.activityInfo.packageName,
319f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                                       ri.activityInfo.name);
320f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            if (ri.match > bestMatch) bestMatch = ri.match;
321f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        }
322f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
323f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        Log.d(LOG_TAG, "Setting preferred web search activity to " + component);
324f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        IntentFilter filter = new IntentFilter(action);
325f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        filter.addCategory(Intent.CATEGORY_DEFAULT);
326f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        pm.replacePreferredActivity(filter, bestMatch, set, component);
327f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        return true;
328f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath    }
329f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
330444c727e0eecf83e9d0b9c4e7af5cbf5fc4135f8Bjorn Bringert    private static ComponentName getPreferredWebSearchActivity(Context context) {
331f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        // Check if we have a preferred web search activity.
332f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
333444c727e0eecf83e9d0b9c4e7af5cbf5fc4135f8Bjorn Bringert        PackageManager pm = context.getPackageManager();
334f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
335f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
336f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        if (ri == null || ri.activityInfo.name.equals(ResolverActivity.class.getName())) {
337f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            Log.d(LOG_TAG, "No preferred activity set for action web search.");
338f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
339f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            // The components in the providers array are checked in the order of declaration so the
340f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            // first one has the highest priority. If the component exists in the system it is set
341f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            // as the preferred activity to handle intent action web search.
342444c727e0eecf83e9d0b9c4e7af5cbf5fc4135f8Bjorn Bringert            String[] preferredActivities = context.getResources().getStringArray(
343f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                    com.android.internal.R.array.default_web_search_providers);
344f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            for (String componentName : preferredActivities) {
345f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                ComponentName component = ComponentName.unflattenFromString(componentName);
346444c727e0eecf83e9d0b9c4e7af5cbf5fc4135f8Bjorn Bringert                if (setPreferredActivity(context, component, Intent.ACTION_WEB_SEARCH)) {
347f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                    return component;
348f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath                }
349f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath            }
35041282a35568b51270440450c46bb31aa00e52caaSatish Sampath        } else {
35141282a35568b51270440450c46bb31aa00e52caaSatish Sampath            // If the current preferred activity is GoogleSearch, and we detect
35241282a35568b51270440450c46bb31aa00e52caaSatish Sampath            // EnhancedGoogleSearch installed as well, set the latter as preferred since that
35341282a35568b51270440450c46bb31aa00e52caaSatish Sampath            // is a superset and provides more functionality.
35441282a35568b51270440450c46bb31aa00e52caaSatish Sampath            ComponentName cn = new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name);
35541282a35568b51270440450c46bb31aa00e52caaSatish Sampath            if (cn.flattenToShortString().equals(GOOGLE_SEARCH_COMPONENT_NAME)) {
35641282a35568b51270440450c46bb31aa00e52caaSatish Sampath                ComponentName enhancedGoogleSearch = ComponentName.unflattenFromString(
35741282a35568b51270440450c46bb31aa00e52caaSatish Sampath                        ENHANCED_GOOGLE_SEARCH_COMPONENT_NAME);
358444c727e0eecf83e9d0b9c4e7af5cbf5fc4135f8Bjorn Bringert                if (setPreferredActivity(context, enhancedGoogleSearch,
359444c727e0eecf83e9d0b9c4e7af5cbf5fc4135f8Bjorn Bringert                        Intent.ACTION_WEB_SEARCH)) {
36041282a35568b51270440450c46bb31aa00e52caaSatish Sampath                    return enhancedGoogleSearch;
36141282a35568b51270440450c46bb31aa00e52caaSatish Sampath                }
36241282a35568b51270440450c46bb31aa00e52caaSatish Sampath            }
363f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        }
364f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
365f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        if (ri == null) return null;
366f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        return new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name);
367f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath    }
368f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
369875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    /**
370875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     * Returns the list of searchable activities.
371875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen     */
372875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    public synchronized ArrayList<SearchableInfo> getSearchablesList() {
373875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        ArrayList<SearchableInfo> result = new ArrayList<SearchableInfo>(mSearchablesList);
374875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen        return result;
375875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen    }
376f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
3776d72e029cb6e5a9cf26aa3314c3dca83614fc91bBjorn Bringert    /**
3786d72e029cb6e5a9cf26aa3314c3dca83614fc91bBjorn Bringert     * Returns a list of the searchable activities that can be included in global search.
3796d72e029cb6e5a9cf26aa3314c3dca83614fc91bBjorn Bringert     */
3806d72e029cb6e5a9cf26aa3314c3dca83614fc91bBjorn Bringert    public synchronized ArrayList<SearchableInfo> getSearchablesInGlobalSearchList() {
3816d72e029cb6e5a9cf26aa3314c3dca83614fc91bBjorn Bringert        return new ArrayList<SearchableInfo>(mSearchablesInGlobalSearchList);
3826d72e029cb6e5a9cf26aa3314c3dca83614fc91bBjorn Bringert    }
383f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
384f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath    /**
385f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     * Returns a list of the searchable activities that handle web searches.
386f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     */
387f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath    public synchronized ArrayList<SearchableInfo> getSearchablesForWebSearchList() {
388f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        return new ArrayList<SearchableInfo>(mSearchablesForWebSearchList);
389f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath    }
390f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
391f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath    /**
392f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     * Returns the default searchable activity for web searches.
393f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     */
394f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath    public synchronized SearchableInfo getDefaultSearchableForWebSearch() {
395f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        return mDefaultSearchableForWebSearch;
396f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath    }
397f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath
398f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath    /**
399f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     * Sets the default searchable activity for web searches.
400f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath     */
401f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath    public synchronized void setDefaultWebSearch(ComponentName component) {
402444c727e0eecf83e9d0b9c4e7af5cbf5fc4135f8Bjorn Bringert        setPreferredActivity(mContext, component, Intent.ACTION_WEB_SEARCH);
403f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath        buildSearchableList();
404f9acde27486bcc6eea1092073f7b47c31749efd6Satish Sampath    }
405875d50a4b9294b2be33cff6493cae7acd1d07ea7Karl Rosaen}
406