blob: 72efca5eb4b9ee6f2ee79573883470dd9a025f92 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2006 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 com.android.server;
18
Dianne Hackborn1d442e02009-04-20 18:14:05 -070019import java.io.PrintWriter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020import java.util.ArrayList;
21import java.util.Collections;
22import java.util.Comparator;
23import java.util.HashMap;
24import java.util.HashSet;
25import java.util.Iterator;
26import java.util.List;
27import java.util.Map;
28import java.util.Set;
29
30import android.util.Log;
31import android.util.LogPrinter;
32import android.util.Printer;
33
34import android.util.Config;
35import android.content.ContentResolver;
36import android.content.Intent;
37import android.content.IntentFilter;
38
39/**
40 * {@hide}
41 */
42public class IntentResolver<F extends IntentFilter, R extends Object> {
43 final private static String TAG = "IntentResolver";
44 final private static boolean DEBUG = false;
45 final private static boolean localLOGV = DEBUG || Config.LOGV;
46
47 public void addFilter(F f) {
48 if (localLOGV) {
49 Log.v(TAG, "Adding filter: " + f);
50 f.dump(new LogPrinter(Log.VERBOSE, TAG), " ");
51 Log.v(TAG, " Building Lookup Maps:");
52 }
53
54 mFilters.add(f);
55 int numS = register_intent_filter(f, f.schemesIterator(),
56 mSchemeToFilter, " Scheme: ");
57 int numT = register_mime_types(f, " Type: ");
58 if (numS == 0 && numT == 0) {
59 register_intent_filter(f, f.actionsIterator(),
60 mActionToFilter, " Action: ");
61 }
62 if (numT != 0) {
63 register_intent_filter(f, f.actionsIterator(),
64 mTypedActionToFilter, " TypedAction: ");
65 }
66 }
67
68 public void removeFilter(F f) {
69 removeFilterInternal(f);
70 mFilters.remove(f);
71 }
72
73 void removeFilterInternal(F f) {
74 if (localLOGV) {
75 Log.v(TAG, "Removing filter: " + f);
76 f.dump(new LogPrinter(Log.VERBOSE, TAG), " ");
77 Log.v(TAG, " Cleaning Lookup Maps:");
78 }
79
80 int numS = unregister_intent_filter(f, f.schemesIterator(),
81 mSchemeToFilter, " Scheme: ");
82 int numT = unregister_mime_types(f, " Type: ");
83 if (numS == 0 && numT == 0) {
84 unregister_intent_filter(f, f.actionsIterator(),
85 mActionToFilter, " Action: ");
86 }
87 if (numT != 0) {
88 unregister_intent_filter(f, f.actionsIterator(),
89 mTypedActionToFilter, " TypedAction: ");
90 }
91 }
92
Dianne Hackborn1d442e02009-04-20 18:14:05 -070093 void dumpMap(PrintWriter out, String prefix, Map<String, ArrayList<F>> map) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080094 String eprefix = prefix + " ";
95 String fprefix = prefix + " ";
96 for (Map.Entry<String, ArrayList<F>> e : map.entrySet()) {
Dianne Hackborn1d442e02009-04-20 18:14:05 -070097 out.print(eprefix); out.print(e.getKey()); out.println(":");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080098 ArrayList<F> a = e.getValue();
99 final int N = a.size();
100 for (int i=0; i<N; i++) {
101 dumpFilter(out, fprefix, a.get(i));
102 }
103 }
104 }
105
Dianne Hackborn1d442e02009-04-20 18:14:05 -0700106 public void dump(PrintWriter out, String prefix) {
107 String innerPrefix = prefix + " ";
108 out.print(prefix); out.println("Full MIME Types:");
109 dumpMap(out, innerPrefix, mTypeToFilter);
110 out.println(" ");
111 out.print(prefix); out.println("Base MIME Types:");
112 dumpMap(out, innerPrefix, mBaseTypeToFilter);
113 out.println(" ");
114 out.print(prefix); out.println("Wild MIME Types:");
115 dumpMap(out, innerPrefix, mWildTypeToFilter);
116 out.println(" ");
117 out.print(prefix); out.println("Schemes:");
118 dumpMap(out, innerPrefix, mSchemeToFilter);
119 out.println(" ");
120 out.print(prefix); out.println("Non-Data Actions:");
121 dumpMap(out, innerPrefix, mActionToFilter);
122 out.println(" ");
123 out.print(prefix); out.println("MIME Typed Actions:");
124 dumpMap(out, innerPrefix, mTypedActionToFilter);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800125 }
126
127 private class IteratorWrapper implements Iterator<F> {
128 private final Iterator<F> mI;
129 private F mCur;
130
131 IteratorWrapper(Iterator<F> it) {
132 mI = it;
133 }
134
135 public boolean hasNext() {
136 return mI.hasNext();
137 }
138
139 public F next() {
140 return (mCur = mI.next());
141 }
142
143 public void remove() {
144 if (mCur != null) {
145 removeFilterInternal(mCur);
146 }
147 mI.remove();
148 }
149
150 }
151
152 /**
153 * Returns an iterator allowing filters to be removed.
154 */
155 public Iterator<F> filterIterator() {
156 return new IteratorWrapper(mFilters.iterator());
157 }
158
159 /**
160 * Returns a read-only set of the filters.
161 */
162 public Set<F> filterSet() {
163 return Collections.unmodifiableSet(mFilters);
164 }
165
166 public List<R> queryIntent(ContentResolver resolver, Intent intent,
167 String resolvedType, boolean defaultOnly) {
168 String scheme = intent.getScheme();
169
170 ArrayList<R> finalList = new ArrayList<R>();
171
172 final boolean debug = localLOGV ||
173 ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
174
175 if (debug) Log.v(
176 TAG, "Resolving type " + resolvedType + " scheme " + scheme
177 + " of intent " + intent);
178
179 ArrayList<F> firstTypeCut = null;
180 ArrayList<F> secondTypeCut = null;
181 ArrayList<F> thirdTypeCut = null;
182 ArrayList<F> schemeCut = null;
183
184 // If the intent includes a MIME type, then we want to collect all of
185 // the filters that match that MIME type.
186 if (resolvedType != null) {
187 int slashpos = resolvedType.indexOf('/');
188 if (slashpos > 0) {
189 final String baseType = resolvedType.substring(0, slashpos);
190 if (!baseType.equals("*")) {
191 if (resolvedType.length() != slashpos+2
192 || resolvedType.charAt(slashpos+1) != '*') {
193 // Not a wild card, so we can just look for all filters that
194 // completely match or wildcards whose base type matches.
195 firstTypeCut = mTypeToFilter.get(resolvedType);
196 if (debug) Log.v(TAG, "First type cut: " + firstTypeCut);
197 secondTypeCut = mWildTypeToFilter.get(baseType);
198 if (debug) Log.v(TAG, "Second type cut: " + secondTypeCut);
199 } else {
200 // We can match anything with our base type.
201 firstTypeCut = mBaseTypeToFilter.get(baseType);
202 if (debug) Log.v(TAG, "First type cut: " + firstTypeCut);
203 secondTypeCut = mWildTypeToFilter.get(baseType);
204 if (debug) Log.v(TAG, "Second type cut: " + secondTypeCut);
205 }
206 // Any */* types always apply, but we only need to do this
207 // if the intent type was not already */*.
208 thirdTypeCut = mWildTypeToFilter.get("*");
209 if (debug) Log.v(TAG, "Third type cut: " + thirdTypeCut);
210 } else if (intent.getAction() != null) {
211 // The intent specified any type ({@literal *}/*). This
212 // can be a whole heck of a lot of things, so as a first
213 // cut let's use the action instead.
214 firstTypeCut = mTypedActionToFilter.get(intent.getAction());
215 if (debug) Log.v(TAG, "Typed Action list: " + firstTypeCut);
216 }
217 }
218 }
219
220 // If the intent includes a data URI, then we want to collect all of
221 // the filters that match its scheme (we will further refine matches
222 // on the authority and path by directly matching each resulting filter).
223 if (scheme != null) {
224 schemeCut = mSchemeToFilter.get(scheme);
225 if (debug) Log.v(TAG, "Scheme list: " + schemeCut);
226 }
227
228 // If the intent does not specify any data -- either a MIME type or
229 // a URI -- then we will only be looking for matches against empty
230 // data.
231 if (resolvedType == null && scheme == null && intent.getAction() != null) {
232 firstTypeCut = mActionToFilter.get(intent.getAction());
233 if (debug) Log.v(TAG, "Action list: " + firstTypeCut);
234 }
235
236 if (firstTypeCut != null) {
237 buildResolveList(intent, debug, defaultOnly,
238 resolvedType, scheme, firstTypeCut, finalList);
239 }
240 if (secondTypeCut != null) {
241 buildResolveList(intent, debug, defaultOnly,
242 resolvedType, scheme, secondTypeCut, finalList);
243 }
244 if (thirdTypeCut != null) {
245 buildResolveList(intent, debug, defaultOnly,
246 resolvedType, scheme, thirdTypeCut, finalList);
247 }
248 if (schemeCut != null) {
249 buildResolveList(intent, debug, defaultOnly,
250 resolvedType, scheme, schemeCut, finalList);
251 }
252 sortResults(finalList);
253
254 if (debug) {
255 Log.v(TAG, "Final result list:");
256 for (R r : finalList) {
257 Log.v(TAG, " " + r);
258 }
259 }
260 return finalList;
261 }
262
263 /**
264 * Control whether the given filter is allowed to go into the result
265 * list. Mainly intended to prevent adding multiple filters for the
266 * same target object.
267 */
268 protected boolean allowFilterResult(F filter, List<R> dest) {
269 return true;
270 }
271
272 protected R newResult(F filter, int match) {
273 return (R)filter;
274 }
275
276 protected void sortResults(List<R> results) {
277 Collections.sort(results, mResolvePrioritySorter);
278 }
279
Dianne Hackborn1d442e02009-04-20 18:14:05 -0700280 protected void dumpFilter(PrintWriter out, String prefix, F filter) {
281 out.print(prefix); out.println(filter);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800282 }
283
284 private final int register_mime_types(F filter, String prefix) {
285 final Iterator<String> i = filter.typesIterator();
286 if (i == null) {
287 return 0;
288 }
289
290 int num = 0;
291 while (i.hasNext()) {
292 String name = (String)i.next();
293 num++;
294 if (localLOGV) Log.v(TAG, prefix + name);
295 String baseName = name;
296 final int slashpos = name.indexOf('/');
297 if (slashpos > 0) {
298 baseName = name.substring(0, slashpos).intern();
299 } else {
300 name = name + "/*";
301 }
302
303 ArrayList<F> array = mTypeToFilter.get(name);
304 if (array == null) {
305 //Log.v(TAG, "Creating new array for " + name);
306 array = new ArrayList<F>();
307 mTypeToFilter.put(name, array);
308 }
309 array.add(filter);
310
311 if (slashpos > 0) {
312 array = mBaseTypeToFilter.get(baseName);
313 if (array == null) {
314 //Log.v(TAG, "Creating new array for " + name);
315 array = new ArrayList<F>();
316 mBaseTypeToFilter.put(baseName, array);
317 }
318 array.add(filter);
319 } else {
320 array = mWildTypeToFilter.get(baseName);
321 if (array == null) {
322 //Log.v(TAG, "Creating new array for " + name);
323 array = new ArrayList<F>();
324 mWildTypeToFilter.put(baseName, array);
325 }
326 array.add(filter);
327 }
328 }
329
330 return num;
331 }
332
333 private final int unregister_mime_types(F filter, String prefix) {
334 final Iterator<String> i = filter.typesIterator();
335 if (i == null) {
336 return 0;
337 }
338
339 int num = 0;
340 while (i.hasNext()) {
341 String name = (String)i.next();
342 num++;
343 if (localLOGV) Log.v(TAG, prefix + name);
344 String baseName = name;
345 final int slashpos = name.indexOf('/');
346 if (slashpos > 0) {
347 baseName = name.substring(0, slashpos).intern();
348 } else {
349 name = name + "/*";
350 }
351
352 if (!remove_all_objects(mTypeToFilter.get(name), filter)) {
353 mTypeToFilter.remove(name);
354 }
355
356 if (slashpos > 0) {
357 if (!remove_all_objects(mBaseTypeToFilter.get(baseName), filter)) {
358 mBaseTypeToFilter.remove(baseName);
359 }
360 } else {
361 if (!remove_all_objects(mWildTypeToFilter.get(baseName), filter)) {
362 mWildTypeToFilter.remove(baseName);
363 }
364 }
365 }
366 return num;
367 }
368
369 private final int register_intent_filter(F filter, Iterator<String> i,
370 HashMap<String, ArrayList<F>> dest, String prefix) {
371 if (i == null) {
372 return 0;
373 }
374
375 int num = 0;
376 while (i.hasNext()) {
377 String name = i.next();
378 num++;
379 if (localLOGV) Log.v(TAG, prefix + name);
380 ArrayList<F> array = dest.get(name);
381 if (array == null) {
382 //Log.v(TAG, "Creating new array for " + name);
383 array = new ArrayList<F>();
384 dest.put(name, array);
385 }
386 array.add(filter);
387 }
388 return num;
389 }
390
391 private final int unregister_intent_filter(F filter, Iterator<String> i,
392 HashMap<String, ArrayList<F>> dest, String prefix) {
393 if (i == null) {
394 return 0;
395 }
396
397 int num = 0;
398 while (i.hasNext()) {
399 String name = i.next();
400 num++;
401 if (localLOGV) Log.v(TAG, prefix + name);
402 if (!remove_all_objects(dest.get(name), filter)) {
403 dest.remove(name);
404 }
405 }
406 return num;
407 }
408
409 private final boolean remove_all_objects(List<F> list, Object object) {
410 if (list != null) {
411 int N = list.size();
412 for (int idx=0; idx<N; idx++) {
413 if (list.get(idx) == object) {
414 list.remove(idx);
415 idx--;
416 N--;
417 }
418 }
419 return N > 0;
420 }
421 return false;
422 }
423
424 private void buildResolveList(Intent intent, boolean debug, boolean defaultOnly,
425 String resolvedType, String scheme, List<F> src, List<R> dest) {
426 Set<String> categories = intent.getCategories();
427
428 final int N = src != null ? src.size() : 0;
429 boolean hasNonDefaults = false;
430 int i;
431 for (i=0; i<N; i++) {
432 F filter = src.get(i);
433 int match;
434 if (debug) Log.v(TAG, "Matching against filter " + filter);
435
436 // Do we already have this one?
437 if (!allowFilterResult(filter, dest)) {
438 if (debug) {
439 Log.v(TAG, " Filter's target already added");
440 }
441 continue;
442 }
443
444 match = filter.match(
445 intent.getAction(), resolvedType, scheme, intent.getData(), categories, TAG);
446 if (match >= 0) {
447 if (debug) Log.v(TAG, " Filter matched! match=0x" +
448 Integer.toHexString(match));
449 if (!defaultOnly || filter.hasCategory(Intent.CATEGORY_DEFAULT)) {
450 final R oneResult = newResult(filter, match);
451 if (oneResult != null) {
452 dest.add(oneResult);
453 }
454 } else {
455 hasNonDefaults = true;
456 }
457 } else {
458 if (debug) {
459 String reason;
460 switch (match) {
461 case IntentFilter.NO_MATCH_ACTION: reason = "action"; break;
462 case IntentFilter.NO_MATCH_CATEGORY: reason = "category"; break;
463 case IntentFilter.NO_MATCH_DATA: reason = "data"; break;
464 case IntentFilter.NO_MATCH_TYPE: reason = "type"; break;
465 default: reason = "unknown reason"; break;
466 }
467 Log.v(TAG, " Filter did not match: " + reason);
468 }
469 }
470 }
471
472 if (dest.size() == 0 && hasNonDefaults) {
473 Log.w(TAG, "resolveIntent failed: found match, but none with Intent.CATEGORY_DEFAULT");
474 }
475 }
476
477 // Sorts a List of IntentFilter objects into descending priority order.
478 private static final Comparator mResolvePrioritySorter = new Comparator() {
479 public int compare(Object o1, Object o2) {
480 float q1 = ((IntentFilter)o1).getPriority();
481 float q2 = ((IntentFilter)o2).getPriority();
482 return (q1 > q2) ? -1 : ((q1 < q2) ? 1 : 0);
483 }
484 };
485
486 /**
487 * All filters that have been registered.
488 */
489 private final HashSet<F> mFilters = new HashSet<F>();
490
491 /**
492 * All of the MIME types that have been registered, such as "image/jpeg",
493 * "image/*", or "{@literal *}/*".
494 */
495 private final HashMap<String, ArrayList<F>> mTypeToFilter
496 = new HashMap<String, ArrayList<F>>();
497
498 /**
499 * The base names of all of all fully qualified MIME types that have been
500 * registered, such as "image" or "*". Wild card MIME types such as
501 * "image/*" will not be here.
502 */
503 private final HashMap<String, ArrayList<F>> mBaseTypeToFilter
504 = new HashMap<String, ArrayList<F>>();
505
506 /**
507 * The base names of all of the MIME types with a sub-type wildcard that
508 * have been registered. For example, a filter with "image/*" will be
509 * included here as "image" but one with "image/jpeg" will not be
510 * included here. This also includes the "*" for the "{@literal *}/*"
511 * MIME type.
512 */
513 private final HashMap<String, ArrayList<F>> mWildTypeToFilter
514 = new HashMap<String, ArrayList<F>>();
515
516 /**
517 * All of the URI schemes (such as http) that have been registered.
518 */
519 private final HashMap<String, ArrayList<F>> mSchemeToFilter
520 = new HashMap<String, ArrayList<F>>();
521
522 /**
523 * All of the actions that have been registered, but only those that did
524 * not specify data.
525 */
526 private final HashMap<String, ArrayList<F>> mActionToFilter
527 = new HashMap<String, ArrayList<F>>();
528
529 /**
530 * All of the actions that have been registered and specified a MIME type.
531 */
532 private final HashMap<String, ArrayList<F>> mTypedActionToFilter
533 = new HashMap<String, ArrayList<F>>();
534}
535