blob: 9586d56e8e8d6811c9234eb151da9833e4736c09 [file] [log] [blame]
Karl Rosaen875d50a2009-04-23 19:00:21 -07001/*
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
Bjorn Bringert74708bb2009-04-28 11:26:52 +010019import android.app.SearchManager;
Karl Rosaen875d50a2009-04-23 19:00:21 -070020import 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;
Bjorn Bringert6d72e022009-04-29 14:56:12 +010047 private ArrayList<SearchableInfo> mSearchablesInGlobalSearchList = null;
Karl Rosaen875d50a2009-04-23 19:00:21 -070048 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 /**
Karl Rosaen875d50a2009-04-23 19:00:21 -0700153 * 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
Bjorn Bringert74708bb2009-04-28 11:26:52 +0100188 // These will become the new values at the end of the method
Karl Rosaen875d50a2009-04-23 19:00:21 -0700189 HashMap<ComponentName, SearchableInfo> newSearchablesMap
190 = new HashMap<ComponentName, SearchableInfo>();
191 ArrayList<SearchableInfo> newSearchablesList
192 = new ArrayList<SearchableInfo>();
Bjorn Bringert6d72e022009-04-29 14:56:12 +0100193 ArrayList<SearchableInfo> newSearchablesInGlobalSearchList
194 = new ArrayList<SearchableInfo>();
Karl Rosaen875d50a2009-04-23 19:00:21 -0700195
Karl Rosaen875d50a2009-04-23 19:00:21 -0700196 final PackageManager pm = mContext.getPackageManager();
Bjorn Bringert74708bb2009-04-28 11:26:52 +0100197
198 // use intent resolver to generate list of ACTION_SEARCH receivers
Karl Rosaen875d50a2009-04-23 19:00:21 -0700199 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);
Bjorn Bringerta9204132009-05-05 14:06:35 +0100213 newSearchablesMap.put(searchable.getSearchActivity(), searchable);
Bjorn Bringert6d72e022009-04-29 14:56:12 +0100214 if (searchable.shouldIncludeInGlobalSearch()) {
215 newSearchablesInGlobalSearchList.add(searchable);
216 }
Karl Rosaen875d50a2009-04-23 19:00:21 -0700217 }
218 }
219 }
220
Bjorn Bringert74708bb2009-04-28 11:26:52 +0100221 // 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
Karl Rosaen875d50a2009-04-23 19:00:21 -0700227 synchronized (this) {
Karl Rosaen875d50a2009-04-23 19:00:21 -0700228 mSearchablesMap = newSearchablesMap;
Bjorn Bringert6d72e022009-04-29 14:56:12 +0100229 mSearchablesList = newSearchablesList;
230 mSearchablesInGlobalSearchList = newSearchablesInGlobalSearchList;
Bjorn Bringert74708bb2009-04-28 11:26:52 +0100231 mDefaultSearchable = newDefaultSearchable;
Karl Rosaen875d50a2009-04-23 19:00:21 -0700232 }
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 }
Bjorn Bringert6d72e022009-04-29 14:56:12 +0100242
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 }
Karl Rosaen875d50a2009-04-23 19:00:21 -0700249}