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