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