blob: 53e63c272c7290ac49a6fbbaf8da9466829c1979 [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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800183 public List<R> queryIntent(ContentResolver resolver, Intent intent,
184 String resolvedType, boolean defaultOnly) {
185 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
192 if (debug) Log.v(
193 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);
213 if (debug) Log.v(TAG, "First type cut: " + firstTypeCut);
214 secondTypeCut = mWildTypeToFilter.get(baseType);
215 if (debug) Log.v(TAG, "Second type cut: " + secondTypeCut);
216 } else {
217 // We can match anything with our base type.
218 firstTypeCut = mBaseTypeToFilter.get(baseType);
219 if (debug) Log.v(TAG, "First type cut: " + firstTypeCut);
220 secondTypeCut = mWildTypeToFilter.get(baseType);
221 if (debug) Log.v(TAG, "Second type cut: " + secondTypeCut);
222 }
223 // Any */* types always apply, but we only need to do this
224 // if the intent type was not already */*.
225 thirdTypeCut = mWildTypeToFilter.get("*");
226 if (debug) Log.v(TAG, "Third type cut: " + thirdTypeCut);
227 } 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());
232 if (debug) Log.v(TAG, "Typed Action list: " + firstTypeCut);
233 }
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);
242 if (debug) Log.v(TAG, "Scheme list: " + schemeCut);
243 }
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());
250 if (debug) Log.v(TAG, "Action list: " + firstTypeCut);
251 }
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) {
272 Log.v(TAG, "Final result list:");
273 for (R r : finalList) {
274 Log.v(TAG, " " + r);
275 }
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++;
311 if (localLOGV) Log.v(TAG, prefix + name);
312 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) {
322 //Log.v(TAG, "Creating new array for " + name);
323 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) {
331 //Log.v(TAG, "Creating new array for " + name);
332 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) {
339 //Log.v(TAG, "Creating new array for " + name);
340 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++;
360 if (localLOGV) Log.v(TAG, prefix + name);
361 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++;
396 if (localLOGV) Log.v(TAG, prefix + name);
397 ArrayList<F> array = dest.get(name);
398 if (array == null) {
399 //Log.v(TAG, "Creating new array for " + name);
400 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++;
418 if (localLOGV) Log.v(TAG, prefix + name);
419 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;
451 if (debug) Log.v(TAG, "Matching against filter " + filter);
452
453 // Do we already have this one?
454 if (!allowFilterResult(filter, dest)) {
455 if (debug) {
456 Log.v(TAG, " Filter's target already added");
457 }
458 continue;
459 }
460
461 match = filter.match(
462 intent.getAction(), resolvedType, scheme, intent.getData(), categories, TAG);
463 if (match >= 0) {
464 if (debug) Log.v(TAG, " Filter matched! match=0x" +
465 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 }
484 Log.v(TAG, " Filter did not match: " + reason);
485 }
486 }
487 }
488
489 if (dest.size() == 0 && hasNonDefaults) {
490 Log.w(TAG, "resolveIntent failed: found match, but none with Intent.CATEGORY_DEFAULT");
491 }
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