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