blob: d8c8c90fd2547fa5bfaa4fb68ac91a5050140782 [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
Mihai Predaeae850c2009-05-13 10:13:48 +0200166 public List<R> queryIntentFromList(Intent intent, String resolvedType,
167 boolean defaultOnly, ArrayList<ArrayList<F>> listCut) {
168 ArrayList<R> resultList = new ArrayList<R>();
169
170 final boolean debug = localLOGV ||
171 ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
172
173 final String scheme = intent.getScheme();
174 int N = listCut.size();
175 for (int i = 0; i < N; ++i) {
176 buildResolveList(intent, debug, defaultOnly,
177 resolvedType, scheme, listCut.get(i), resultList);
178 }
179 sortResults(resultList);
180 return resultList;
181 }
182
Mihai Preda074edef2009-05-18 17:13:31 +0200183 public List<R> queryIntent(Intent intent, String resolvedType, boolean defaultOnly) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800184 String scheme = intent.getScheme();
185
186 ArrayList<R> finalList = new ArrayList<R>();
187
188 final boolean debug = localLOGV ||
189 ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
190
191 if (debug) Log.v(
192 TAG, "Resolving type " + resolvedType + " scheme " + scheme
193 + " of intent " + intent);
194
195 ArrayList<F> firstTypeCut = null;
196 ArrayList<F> secondTypeCut = null;
197 ArrayList<F> thirdTypeCut = null;
198 ArrayList<F> schemeCut = null;
199
200 // If the intent includes a MIME type, then we want to collect all of
201 // the filters that match that MIME type.
202 if (resolvedType != null) {
203 int slashpos = resolvedType.indexOf('/');
204 if (slashpos > 0) {
205 final String baseType = resolvedType.substring(0, slashpos);
206 if (!baseType.equals("*")) {
207 if (resolvedType.length() != slashpos+2
208 || resolvedType.charAt(slashpos+1) != '*') {
209 // Not a wild card, so we can just look for all filters that
210 // completely match or wildcards whose base type matches.
211 firstTypeCut = mTypeToFilter.get(resolvedType);
212 if (debug) Log.v(TAG, "First type cut: " + firstTypeCut);
213 secondTypeCut = mWildTypeToFilter.get(baseType);
214 if (debug) Log.v(TAG, "Second type cut: " + secondTypeCut);
215 } else {
216 // We can match anything with our base type.
217 firstTypeCut = mBaseTypeToFilter.get(baseType);
218 if (debug) Log.v(TAG, "First type cut: " + firstTypeCut);
219 secondTypeCut = mWildTypeToFilter.get(baseType);
220 if (debug) Log.v(TAG, "Second type cut: " + secondTypeCut);
221 }
222 // Any */* types always apply, but we only need to do this
223 // if the intent type was not already */*.
224 thirdTypeCut = mWildTypeToFilter.get("*");
225 if (debug) Log.v(TAG, "Third type cut: " + thirdTypeCut);
226 } else if (intent.getAction() != null) {
227 // The intent specified any type ({@literal *}/*). This
228 // can be a whole heck of a lot of things, so as a first
229 // cut let's use the action instead.
230 firstTypeCut = mTypedActionToFilter.get(intent.getAction());
231 if (debug) Log.v(TAG, "Typed Action list: " + firstTypeCut);
232 }
233 }
234 }
235
236 // If the intent includes a data URI, then we want to collect all of
237 // the filters that match its scheme (we will further refine matches
238 // on the authority and path by directly matching each resulting filter).
239 if (scheme != null) {
240 schemeCut = mSchemeToFilter.get(scheme);
241 if (debug) Log.v(TAG, "Scheme list: " + schemeCut);
242 }
243
244 // If the intent does not specify any data -- either a MIME type or
245 // a URI -- then we will only be looking for matches against empty
246 // data.
247 if (resolvedType == null && scheme == null && intent.getAction() != null) {
248 firstTypeCut = mActionToFilter.get(intent.getAction());
249 if (debug) Log.v(TAG, "Action list: " + firstTypeCut);
250 }
251
252 if (firstTypeCut != null) {
253 buildResolveList(intent, debug, defaultOnly,
254 resolvedType, scheme, firstTypeCut, finalList);
255 }
256 if (secondTypeCut != null) {
257 buildResolveList(intent, debug, defaultOnly,
258 resolvedType, scheme, secondTypeCut, finalList);
259 }
260 if (thirdTypeCut != null) {
261 buildResolveList(intent, debug, defaultOnly,
262 resolvedType, scheme, thirdTypeCut, finalList);
263 }
264 if (schemeCut != null) {
265 buildResolveList(intent, debug, defaultOnly,
266 resolvedType, scheme, schemeCut, finalList);
267 }
268 sortResults(finalList);
269
270 if (debug) {
271 Log.v(TAG, "Final result list:");
272 for (R r : finalList) {
273 Log.v(TAG, " " + r);
274 }
275 }
276 return finalList;
277 }
278
279 /**
280 * Control whether the given filter is allowed to go into the result
281 * list. Mainly intended to prevent adding multiple filters for the
282 * same target object.
283 */
284 protected boolean allowFilterResult(F filter, List<R> dest) {
285 return true;
286 }
287
288 protected R newResult(F filter, int match) {
289 return (R)filter;
290 }
291
292 protected void sortResults(List<R> results) {
293 Collections.sort(results, mResolvePrioritySorter);
294 }
295
Dianne Hackborn1d442e02009-04-20 18:14:05 -0700296 protected void dumpFilter(PrintWriter out, String prefix, F filter) {
297 out.print(prefix); out.println(filter);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800298 }
299
300 private final int register_mime_types(F filter, String prefix) {
301 final Iterator<String> i = filter.typesIterator();
302 if (i == null) {
303 return 0;
304 }
305
306 int num = 0;
307 while (i.hasNext()) {
308 String name = (String)i.next();
309 num++;
310 if (localLOGV) Log.v(TAG, prefix + name);
311 String baseName = name;
312 final int slashpos = name.indexOf('/');
313 if (slashpos > 0) {
314 baseName = name.substring(0, slashpos).intern();
315 } else {
316 name = name + "/*";
317 }
318
319 ArrayList<F> array = mTypeToFilter.get(name);
320 if (array == null) {
321 //Log.v(TAG, "Creating new array for " + name);
322 array = new ArrayList<F>();
323 mTypeToFilter.put(name, array);
324 }
325 array.add(filter);
326
327 if (slashpos > 0) {
328 array = mBaseTypeToFilter.get(baseName);
329 if (array == null) {
330 //Log.v(TAG, "Creating new array for " + name);
331 array = new ArrayList<F>();
332 mBaseTypeToFilter.put(baseName, array);
333 }
334 array.add(filter);
335 } else {
336 array = mWildTypeToFilter.get(baseName);
337 if (array == null) {
338 //Log.v(TAG, "Creating new array for " + name);
339 array = new ArrayList<F>();
340 mWildTypeToFilter.put(baseName, array);
341 }
342 array.add(filter);
343 }
344 }
345
346 return num;
347 }
348
349 private final int unregister_mime_types(F filter, String prefix) {
350 final Iterator<String> i = filter.typesIterator();
351 if (i == null) {
352 return 0;
353 }
354
355 int num = 0;
356 while (i.hasNext()) {
357 String name = (String)i.next();
358 num++;
359 if (localLOGV) Log.v(TAG, prefix + name);
360 String baseName = name;
361 final int slashpos = name.indexOf('/');
362 if (slashpos > 0) {
363 baseName = name.substring(0, slashpos).intern();
364 } else {
365 name = name + "/*";
366 }
367
368 if (!remove_all_objects(mTypeToFilter.get(name), filter)) {
369 mTypeToFilter.remove(name);
370 }
371
372 if (slashpos > 0) {
373 if (!remove_all_objects(mBaseTypeToFilter.get(baseName), filter)) {
374 mBaseTypeToFilter.remove(baseName);
375 }
376 } else {
377 if (!remove_all_objects(mWildTypeToFilter.get(baseName), filter)) {
378 mWildTypeToFilter.remove(baseName);
379 }
380 }
381 }
382 return num;
383 }
384
385 private final int register_intent_filter(F filter, Iterator<String> i,
386 HashMap<String, ArrayList<F>> dest, String prefix) {
387 if (i == null) {
388 return 0;
389 }
390
391 int num = 0;
392 while (i.hasNext()) {
393 String name = i.next();
394 num++;
395 if (localLOGV) Log.v(TAG, prefix + name);
396 ArrayList<F> array = dest.get(name);
397 if (array == null) {
398 //Log.v(TAG, "Creating new array for " + name);
399 array = new ArrayList<F>();
400 dest.put(name, array);
401 }
402 array.add(filter);
403 }
404 return num;
405 }
406
407 private final int unregister_intent_filter(F filter, Iterator<String> i,
408 HashMap<String, ArrayList<F>> dest, String prefix) {
409 if (i == null) {
410 return 0;
411 }
412
413 int num = 0;
414 while (i.hasNext()) {
415 String name = i.next();
416 num++;
417 if (localLOGV) Log.v(TAG, prefix + name);
418 if (!remove_all_objects(dest.get(name), filter)) {
419 dest.remove(name);
420 }
421 }
422 return num;
423 }
424
425 private final boolean remove_all_objects(List<F> list, Object object) {
426 if (list != null) {
427 int N = list.size();
428 for (int idx=0; idx<N; idx++) {
429 if (list.get(idx) == object) {
430 list.remove(idx);
431 idx--;
432 N--;
433 }
434 }
435 return N > 0;
436 }
437 return false;
438 }
439
440 private void buildResolveList(Intent intent, boolean debug, boolean defaultOnly,
441 String resolvedType, String scheme, List<F> src, List<R> dest) {
442 Set<String> categories = intent.getCategories();
443
444 final int N = src != null ? src.size() : 0;
445 boolean hasNonDefaults = false;
446 int i;
447 for (i=0; i<N; i++) {
448 F filter = src.get(i);
449 int match;
450 if (debug) Log.v(TAG, "Matching against filter " + filter);
451
452 // Do we already have this one?
453 if (!allowFilterResult(filter, dest)) {
454 if (debug) {
455 Log.v(TAG, " Filter's target already added");
456 }
457 continue;
458 }
459
460 match = filter.match(
461 intent.getAction(), resolvedType, scheme, intent.getData(), categories, TAG);
462 if (match >= 0) {
463 if (debug) Log.v(TAG, " Filter matched! match=0x" +
464 Integer.toHexString(match));
465 if (!defaultOnly || filter.hasCategory(Intent.CATEGORY_DEFAULT)) {
466 final R oneResult = newResult(filter, match);
467 if (oneResult != null) {
468 dest.add(oneResult);
469 }
470 } else {
471 hasNonDefaults = true;
472 }
473 } else {
474 if (debug) {
475 String reason;
476 switch (match) {
477 case IntentFilter.NO_MATCH_ACTION: reason = "action"; break;
478 case IntentFilter.NO_MATCH_CATEGORY: reason = "category"; break;
479 case IntentFilter.NO_MATCH_DATA: reason = "data"; break;
480 case IntentFilter.NO_MATCH_TYPE: reason = "type"; break;
481 default: reason = "unknown reason"; break;
482 }
483 Log.v(TAG, " Filter did not match: " + reason);
484 }
485 }
486 }
487
488 if (dest.size() == 0 && hasNonDefaults) {
489 Log.w(TAG, "resolveIntent failed: found match, but none with Intent.CATEGORY_DEFAULT");
490 }
491 }
492
493 // Sorts a List of IntentFilter objects into descending priority order.
494 private static final Comparator mResolvePrioritySorter = new Comparator() {
495 public int compare(Object o1, Object o2) {
496 float q1 = ((IntentFilter)o1).getPriority();
497 float q2 = ((IntentFilter)o2).getPriority();
498 return (q1 > q2) ? -1 : ((q1 < q2) ? 1 : 0);
499 }
500 };
501
502 /**
503 * All filters that have been registered.
504 */
505 private final HashSet<F> mFilters = new HashSet<F>();
506
507 /**
508 * All of the MIME types that have been registered, such as "image/jpeg",
509 * "image/*", or "{@literal *}/*".
510 */
511 private final HashMap<String, ArrayList<F>> mTypeToFilter
512 = new HashMap<String, ArrayList<F>>();
513
514 /**
515 * The base names of all of all fully qualified MIME types that have been
516 * registered, such as "image" or "*". Wild card MIME types such as
517 * "image/*" will not be here.
518 */
519 private final HashMap<String, ArrayList<F>> mBaseTypeToFilter
520 = new HashMap<String, ArrayList<F>>();
521
522 /**
523 * The base names of all of the MIME types with a sub-type wildcard that
524 * have been registered. For example, a filter with "image/*" will be
525 * included here as "image" but one with "image/jpeg" will not be
526 * included here. This also includes the "*" for the "{@literal *}/*"
527 * MIME type.
528 */
529 private final HashMap<String, ArrayList<F>> mWildTypeToFilter
530 = new HashMap<String, ArrayList<F>>();
531
532 /**
533 * All of the URI schemes (such as http) that have been registered.
534 */
535 private final HashMap<String, ArrayList<F>> mSchemeToFilter
536 = new HashMap<String, ArrayList<F>>();
537
538 /**
539 * All of the actions that have been registered, but only those that did
540 * not specify data.
541 */
542 private final HashMap<String, ArrayList<F>> mActionToFilter
543 = new HashMap<String, ArrayList<F>>();
544
545 /**
546 * All of the actions that have been registered and specified a MIME type.
547 */
548 private final HashMap<String, ArrayList<F>> mTypedActionToFilter
549 = new HashMap<String, ArrayList<F>>();
550}
551