Searchables.java revision 6d72e029cb6e5a9cf26aa3314c3dca83614fc91b
1/* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.server.search; 18 19import android.app.SearchManager; 20import android.content.ComponentName; 21import android.content.Context; 22import android.content.Intent; 23import android.content.pm.ActivityInfo; 24import android.content.pm.PackageManager; 25import android.content.pm.ResolveInfo; 26import android.os.Bundle; 27 28import java.util.ArrayList; 29import java.util.HashMap; 30import java.util.List; 31 32/** 33 * This class maintains the information about all searchable activities. 34 */ 35public class Searchables { 36 37 // static strings used for XML lookups, etc. 38 // TODO how should these be documented for the developer, in a more structured way than 39 // the current long wordy javadoc in SearchManager.java ? 40 private static final String MD_LABEL_DEFAULT_SEARCHABLE = "android.app.default_searchable"; 41 private static final String MD_SEARCHABLE_SYSTEM_SEARCH = "*"; 42 43 private Context mContext; 44 45 private HashMap<ComponentName, SearchableInfo> mSearchablesMap = null; 46 private ArrayList<SearchableInfo> mSearchablesList = null; 47 private ArrayList<SearchableInfo> mSearchablesInGlobalSearchList = null; 48 private SearchableInfo mDefaultSearchable = null; 49 50 /** 51 * 52 * @param context Context to use for looking up activities etc. 53 */ 54 public Searchables (Context context) { 55 mContext = context; 56 } 57 58 /** 59 * Look up, or construct, based on the activity. 60 * 61 * The activities fall into three cases, based on meta-data found in 62 * the manifest entry: 63 * <ol> 64 * <li>The activity itself implements search. This is indicated by the 65 * presence of a "android.app.searchable" meta-data attribute. 66 * The value is a reference to an XML file containing search information.</li> 67 * <li>A related activity implements search. This is indicated by the 68 * presence of a "android.app.default_searchable" meta-data attribute. 69 * The value is a string naming the activity implementing search. In this 70 * case the factory will "redirect" and return the searchable data.</li> 71 * <li>No searchability data is provided. We return null here and other 72 * code will insert the "default" (e.g. contacts) search. 73 * 74 * TODO: cache the result in the map, and check the map first. 75 * TODO: it might make sense to implement the searchable reference as 76 * an application meta-data entry. This way we don't have to pepper each 77 * and every activity. 78 * TODO: can we skip the constructor step if it's a non-searchable? 79 * TODO: does it make sense to plug the default into a slot here for 80 * automatic return? Probably not, but it's one way to do it. 81 * 82 * @param activity The name of the current activity, or null if the 83 * activity does not define any explicit searchable metadata. 84 */ 85 public SearchableInfo getSearchableInfo(ComponentName activity) { 86 // Step 1. Is the result already hashed? (case 1) 87 SearchableInfo result; 88 synchronized (this) { 89 result = mSearchablesMap.get(activity); 90 if (result != null) return result; 91 } 92 93 // Step 2. See if the current activity references a searchable. 94 // Note: Conceptually, this could be a while(true) loop, but there's 95 // no point in implementing reference chaining here and risking a loop. 96 // References must point directly to searchable activities. 97 98 ActivityInfo ai = null; 99 try { 100 ai = mContext.getPackageManager(). 101 getActivityInfo(activity, PackageManager.GET_META_DATA ); 102 String refActivityName = null; 103 104 // First look for activity-specific reference 105 Bundle md = ai.metaData; 106 if (md != null) { 107 refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE); 108 } 109 // If not found, try for app-wide reference 110 if (refActivityName == null) { 111 md = ai.applicationInfo.metaData; 112 if (md != null) { 113 refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE); 114 } 115 } 116 117 // Irrespective of source, if a reference was found, follow it. 118 if (refActivityName != null) 119 { 120 // An app or activity can declare that we should simply launch 121 // "system default search" if search is invoked. 122 if (refActivityName.equals(MD_SEARCHABLE_SYSTEM_SEARCH)) { 123 return getDefaultSearchable(); 124 } 125 String pkg = activity.getPackageName(); 126 ComponentName referredActivity; 127 if (refActivityName.charAt(0) == '.') { 128 referredActivity = new ComponentName(pkg, pkg + refActivityName); 129 } else { 130 referredActivity = new ComponentName(pkg, refActivityName); 131 } 132 133 // Now try the referred activity, and if found, cache 134 // it against the original name so we can skip the check 135 synchronized (this) { 136 result = mSearchablesMap.get(referredActivity); 137 if (result != null) { 138 mSearchablesMap.put(activity, result); 139 return result; 140 } 141 } 142 } 143 } catch (PackageManager.NameNotFoundException e) { 144 // case 3: no metadata 145 } 146 147 // Step 3. None found. Return null. 148 return null; 149 150 } 151 152 /** 153 * Provides the system-default search activity, which you can use 154 * whenever getSearchableInfo() returns null; 155 * 156 * @return Returns the system-default search activity, null if never defined 157 */ 158 public synchronized SearchableInfo getDefaultSearchable() { 159 return mDefaultSearchable; 160 } 161 162 public synchronized boolean isDefaultSearchable(SearchableInfo searchable) { 163 return searchable == mDefaultSearchable; 164 } 165 166 /** 167 * Builds an entire list (suitable for display) of 168 * activities that are searchable, by iterating the entire set of 169 * ACTION_SEARCH intents. 170 * 171 * Also clears the hash of all activities -> searches which will 172 * refill as the user clicks "search". 173 * 174 * This should only be done at startup and again if we know that the 175 * list has changed. 176 * 177 * TODO: every activity that provides a ACTION_SEARCH intent should 178 * also provide searchability meta-data. There are a bunch of checks here 179 * that, if data is not found, silently skip to the next activity. This 180 * won't help a developer trying to figure out why their activity isn't 181 * showing up in the list, but an exception here is too rough. I would 182 * like to find a better notification mechanism. 183 * 184 * TODO: sort the list somehow? UI choice. 185 */ 186 public void buildSearchableList() { 187 188 // These will become the new values at the end of the method 189 HashMap<ComponentName, SearchableInfo> newSearchablesMap 190 = new HashMap<ComponentName, SearchableInfo>(); 191 ArrayList<SearchableInfo> newSearchablesList 192 = new ArrayList<SearchableInfo>(); 193 ArrayList<SearchableInfo> newSearchablesInGlobalSearchList 194 = new ArrayList<SearchableInfo>(); 195 196 final PackageManager pm = mContext.getPackageManager(); 197 198 // use intent resolver to generate list of ACTION_SEARCH receivers 199 List<ResolveInfo> infoList; 200 final Intent intent = new Intent(Intent.ACTION_SEARCH); 201 infoList = pm.queryIntentActivities(intent, PackageManager.GET_META_DATA); 202 203 // analyze each one, generate a Searchables record, and record 204 if (infoList != null) { 205 int count = infoList.size(); 206 for (int ii = 0; ii < count; ii++) { 207 // for each component, try to find metadata 208 ResolveInfo info = infoList.get(ii); 209 ActivityInfo ai = info.activityInfo; 210 SearchableInfo searchable = SearchableInfo.getActivityMetaData(mContext, ai); 211 if (searchable != null) { 212 newSearchablesList.add(searchable); 213 newSearchablesMap.put(searchable.mSearchActivity, searchable); 214 if (searchable.shouldIncludeInGlobalSearch()) { 215 newSearchablesInGlobalSearchList.add(searchable); 216 } 217 } 218 } 219 } 220 221 // Find the global search provider 222 Intent globalSearchIntent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH); 223 ComponentName globalSearchActivity = globalSearchIntent.resolveActivity(pm); 224 SearchableInfo newDefaultSearchable = newSearchablesMap.get(globalSearchActivity); 225 226 // Store a consistent set of new values 227 synchronized (this) { 228 mSearchablesMap = newSearchablesMap; 229 mSearchablesList = newSearchablesList; 230 mSearchablesInGlobalSearchList = newSearchablesInGlobalSearchList; 231 mDefaultSearchable = newDefaultSearchable; 232 } 233 } 234 235 /** 236 * Returns the list of searchable activities. 237 */ 238 public synchronized ArrayList<SearchableInfo> getSearchablesList() { 239 ArrayList<SearchableInfo> result = new ArrayList<SearchableInfo>(mSearchablesList); 240 return result; 241 } 242 243 /** 244 * Returns a list of the searchable activities that can be included in global search. 245 */ 246 public synchronized ArrayList<SearchableInfo> getSearchablesInGlobalSearchList() { 247 return new ArrayList<SearchableInfo>(mSearchablesInGlobalSearchList); 248 } 249} 250