blob: 94a23796cbb06e8fce0b99862b09cbfb5340a54d [file] [log] [blame]
Dianne Hackborn9ec6cdd2012-05-31 10:57:54 -07001/*
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
19import java.io.PrintWriter;
20import 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.net.Uri;
31import android.util.FastImmutableArraySet;
32import android.util.Log;
33import android.util.PrintWriterPrinter;
34import android.util.Slog;
35import android.util.LogPrinter;
36import android.util.Printer;
37
38import android.content.Intent;
39import android.content.IntentFilter;
40
41/**
42 * Temporary for verification of new implementation.
43 * {@hide}
44 */
45public abstract class IntentResolverOld<F extends IntentFilter, R extends Object> {
46 final private static String TAG = "IntentResolver";
47 final private static boolean DEBUG = false;
48 final private static boolean localLOGV = DEBUG || false;
49
50 public void addFilter(F f) {
51 if (localLOGV) {
52 Slog.v(TAG, "Adding filter: " + f);
53 f.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
54 Slog.v(TAG, " Building Lookup Maps:");
55 }
56
57 mFilters.add(f);
58 int numS = register_intent_filter(f, f.schemesIterator(),
59 mSchemeToFilter, " Scheme: ");
60 int numT = register_mime_types(f, " Type: ");
61 if (numS == 0 && numT == 0) {
62 register_intent_filter(f, f.actionsIterator(),
63 mActionToFilter, " Action: ");
64 }
65 if (numT != 0) {
66 register_intent_filter(f, f.actionsIterator(),
67 mTypedActionToFilter, " TypedAction: ");
68 }
69 }
70
71 public void removeFilter(F f) {
72 removeFilterInternal(f);
73 mFilters.remove(f);
74 }
75
76 void removeFilterInternal(F f) {
77 if (localLOGV) {
78 Slog.v(TAG, "Removing filter: " + f);
79 f.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
80 Slog.v(TAG, " Cleaning Lookup Maps:");
81 }
82
83 int numS = unregister_intent_filter(f, f.schemesIterator(),
84 mSchemeToFilter, " Scheme: ");
85 int numT = unregister_mime_types(f, " Type: ");
86 if (numS == 0 && numT == 0) {
87 unregister_intent_filter(f, f.actionsIterator(),
88 mActionToFilter, " Action: ");
89 }
90 if (numT != 0) {
91 unregister_intent_filter(f, f.actionsIterator(),
92 mTypedActionToFilter, " TypedAction: ");
93 }
94 }
95
96 boolean dumpMap(PrintWriter out, String titlePrefix, String title,
97 String prefix, Map<String, ArrayList<F>> map, String packageName,
98 boolean printFilter) {
99 String eprefix = prefix + " ";
100 String fprefix = prefix + " ";
101 boolean printedSomething = false;
102 Printer printer = null;
103 for (Map.Entry<String, ArrayList<F>> e : map.entrySet()) {
104 ArrayList<F> a = e.getValue();
105 final int N = a.size();
106 boolean printedHeader = false;
107 for (int i=0; i<N; i++) {
108 F filter = a.get(i);
Ben Gruver4efe9402013-04-02 21:18:41 -0700109 if (packageName != null && !isPackageForFilter(packageName, filter)) {
Dianne Hackborn9ec6cdd2012-05-31 10:57:54 -0700110 continue;
111 }
112 if (title != null) {
113 out.print(titlePrefix); out.println(title);
114 title = null;
115 }
116 if (!printedHeader) {
117 out.print(eprefix); out.print(e.getKey()); out.println(":");
118 printedHeader = true;
119 }
120 printedSomething = true;
121 dumpFilter(out, fprefix, filter);
122 if (printFilter) {
123 if (printer == null) {
124 printer = new PrintWriterPrinter(out);
125 }
126 filter.dump(printer, fprefix + " ");
127 }
128 }
129 }
130 return printedSomething;
131 }
132
133 public boolean dump(PrintWriter out, String title, String prefix, String packageName,
134 boolean printFilter) {
135 String innerPrefix = prefix + " ";
136 String sepPrefix = "\n" + prefix;
137 String curPrefix = title + "\n" + prefix;
138 if (dumpMap(out, curPrefix, "Full MIME Types:", innerPrefix,
139 mTypeToFilter, packageName, printFilter)) {
140 curPrefix = sepPrefix;
141 }
142 if (dumpMap(out, curPrefix, "Base MIME Types:", innerPrefix,
143 mBaseTypeToFilter, packageName, printFilter)) {
144 curPrefix = sepPrefix;
145 }
146 if (dumpMap(out, curPrefix, "Wild MIME Types:", innerPrefix,
147 mWildTypeToFilter, packageName, printFilter)) {
148 curPrefix = sepPrefix;
149 }
150 if (dumpMap(out, curPrefix, "Schemes:", innerPrefix,
151 mSchemeToFilter, packageName, printFilter)) {
152 curPrefix = sepPrefix;
153 }
154 if (dumpMap(out, curPrefix, "Non-Data Actions:", innerPrefix,
155 mActionToFilter, packageName, printFilter)) {
156 curPrefix = sepPrefix;
157 }
158 if (dumpMap(out, curPrefix, "MIME Typed Actions:", innerPrefix,
159 mTypedActionToFilter, packageName, printFilter)) {
160 curPrefix = sepPrefix;
161 }
162 return curPrefix == sepPrefix;
163 }
164
165 private class IteratorWrapper implements Iterator<F> {
166 private final Iterator<F> mI;
167 private F mCur;
168
169 IteratorWrapper(Iterator<F> it) {
170 mI = it;
171 }
172
173 public boolean hasNext() {
174 return mI.hasNext();
175 }
176
177 public F next() {
178 return (mCur = mI.next());
179 }
180
181 public void remove() {
182 if (mCur != null) {
183 removeFilterInternal(mCur);
184 }
185 mI.remove();
186 }
187
188 }
189
190 /**
191 * Returns an iterator allowing filters to be removed.
192 */
193 public Iterator<F> filterIterator() {
194 return new IteratorWrapper(mFilters.iterator());
195 }
196
197 /**
198 * Returns a read-only set of the filters.
199 */
200 public Set<F> filterSet() {
201 return Collections.unmodifiableSet(mFilters);
202 }
203
204 public List<R> queryIntentFromList(Intent intent, String resolvedType,
205 boolean defaultOnly, ArrayList<ArrayList<F>> listCut, int userId) {
206 ArrayList<R> resultList = new ArrayList<R>();
207
208 final boolean debug = localLOGV ||
209 ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
210
211 FastImmutableArraySet<String> categories = getFastIntentCategories(intent);
212 final String scheme = intent.getScheme();
213 int N = listCut.size();
214 for (int i = 0; i < N; ++i) {
215 buildResolveList(intent, categories, debug, defaultOnly,
216 resolvedType, scheme, listCut.get(i), resultList, userId);
217 }
218 sortResults(resultList);
219 return resultList;
220 }
221
222 public List<R> queryIntent(Intent intent, String resolvedType, boolean defaultOnly,
223 int userId) {
224 String scheme = intent.getScheme();
225
226 ArrayList<R> finalList = new ArrayList<R>();
227
228 final boolean debug = localLOGV ||
229 ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
230
231 if (debug) Slog.v(
232 TAG, "Resolving type " + resolvedType + " scheme " + scheme
233 + " of intent " + intent);
234
235 ArrayList<F> firstTypeCut = null;
236 ArrayList<F> secondTypeCut = null;
237 ArrayList<F> thirdTypeCut = null;
238 ArrayList<F> schemeCut = null;
239
240 // If the intent includes a MIME type, then we want to collect all of
241 // the filters that match that MIME type.
242 if (resolvedType != null) {
243 int slashpos = resolvedType.indexOf('/');
244 if (slashpos > 0) {
245 final String baseType = resolvedType.substring(0, slashpos);
246 if (!baseType.equals("*")) {
247 if (resolvedType.length() != slashpos+2
248 || resolvedType.charAt(slashpos+1) != '*') {
249 // Not a wild card, so we can just look for all filters that
250 // completely match or wildcards whose base type matches.
251 firstTypeCut = mTypeToFilter.get(resolvedType);
252 if (debug) Slog.v(TAG, "First type cut: " + firstTypeCut);
253 secondTypeCut = mWildTypeToFilter.get(baseType);
254 if (debug) Slog.v(TAG, "Second type cut: " + secondTypeCut);
255 } else {
256 // We can match anything with our base type.
257 firstTypeCut = mBaseTypeToFilter.get(baseType);
258 if (debug) Slog.v(TAG, "First type cut: " + firstTypeCut);
259 secondTypeCut = mWildTypeToFilter.get(baseType);
260 if (debug) Slog.v(TAG, "Second type cut: " + secondTypeCut);
261 }
262 // Any */* types always apply, but we only need to do this
263 // if the intent type was not already */*.
264 thirdTypeCut = mWildTypeToFilter.get("*");
265 if (debug) Slog.v(TAG, "Third type cut: " + thirdTypeCut);
266 } else if (intent.getAction() != null) {
267 // The intent specified any type ({@literal *}/*). This
268 // can be a whole heck of a lot of things, so as a first
269 // cut let's use the action instead.
270 firstTypeCut = mTypedActionToFilter.get(intent.getAction());
271 if (debug) Slog.v(TAG, "Typed Action list: " + firstTypeCut);
272 }
273 }
274 }
275
276 // If the intent includes a data URI, then we want to collect all of
277 // the filters that match its scheme (we will further refine matches
278 // on the authority and path by directly matching each resulting filter).
279 if (scheme != null) {
280 schemeCut = mSchemeToFilter.get(scheme);
281 if (debug) Slog.v(TAG, "Scheme list: " + schemeCut);
282 }
283
284 // If the intent does not specify any data -- either a MIME type or
285 // a URI -- then we will only be looking for matches against empty
286 // data.
287 if (resolvedType == null && scheme == null && intent.getAction() != null) {
288 firstTypeCut = mActionToFilter.get(intent.getAction());
289 if (debug) Slog.v(TAG, "Action list: " + firstTypeCut);
290 }
291
292 FastImmutableArraySet<String> categories = getFastIntentCategories(intent);
293 if (firstTypeCut != null) {
294 buildResolveList(intent, categories, debug, defaultOnly,
295 resolvedType, scheme, firstTypeCut, finalList, userId);
296 }
297 if (secondTypeCut != null) {
298 buildResolveList(intent, categories, debug, defaultOnly,
299 resolvedType, scheme, secondTypeCut, finalList, userId);
300 }
301 if (thirdTypeCut != null) {
302 buildResolveList(intent, categories, debug, defaultOnly,
303 resolvedType, scheme, thirdTypeCut, finalList, userId);
304 }
305 if (schemeCut != null) {
306 buildResolveList(intent, categories, debug, defaultOnly,
307 resolvedType, scheme, schemeCut, finalList, userId);
308 }
309 sortResults(finalList);
310
311 if (debug) {
312 Slog.v(TAG, "Final result list:");
313 for (R r : finalList) {
314 Slog.v(TAG, " " + r);
315 }
316 }
317 return finalList;
318 }
319
320 /**
321 * Control whether the given filter is allowed to go into the result
322 * list. Mainly intended to prevent adding multiple filters for the
323 * same target object.
324 */
325 protected boolean allowFilterResult(F filter, List<R> dest) {
326 return true;
327 }
328
329 /**
330 * Returns whether the object associated with the given filter is
331 * "stopped," that is whether it should not be included in the result
332 * if the intent requests to excluded stopped objects.
333 */
334 protected boolean isFilterStopped(F filter, int userId) {
335 return false;
336 }
337
338 /**
Ben Gruver4efe9402013-04-02 21:18:41 -0700339 * Returns whether this filter is owned by this package. This must be
340 * implemented to provide correct filtering of Intents that have
341 * specified a package name they are to be delivered to.
Dianne Hackborn9ec6cdd2012-05-31 10:57:54 -0700342 */
Ben Gruver4efe9402013-04-02 21:18:41 -0700343 protected abstract boolean isPackageForFilter(String packageName, F filter);
Dianne Hackborn9ec6cdd2012-05-31 10:57:54 -0700344
345 @SuppressWarnings("unchecked")
346 protected R newResult(F filter, int match, int userId) {
347 return (R)filter;
348 }
349
350 @SuppressWarnings("unchecked")
351 protected void sortResults(List<R> results) {
352 Collections.sort(results, mResolvePrioritySorter);
353 }
354
355 protected void dumpFilter(PrintWriter out, String prefix, F filter) {
356 out.print(prefix); out.println(filter);
357 }
358
359 private final int register_mime_types(F filter, String prefix) {
360 final Iterator<String> i = filter.typesIterator();
361 if (i == null) {
362 return 0;
363 }
364
365 int num = 0;
366 while (i.hasNext()) {
367 String name = i.next();
368 num++;
369 if (localLOGV) Slog.v(TAG, prefix + name);
370 String baseName = name;
371 final int slashpos = name.indexOf('/');
372 if (slashpos > 0) {
373 baseName = name.substring(0, slashpos).intern();
374 } else {
375 name = name + "/*";
376 }
377
378 ArrayList<F> array = mTypeToFilter.get(name);
379 if (array == null) {
380 //Slog.v(TAG, "Creating new array for " + name);
381 array = new ArrayList<F>();
382 mTypeToFilter.put(name, array);
383 }
384 array.add(filter);
385
386 if (slashpos > 0) {
387 array = mBaseTypeToFilter.get(baseName);
388 if (array == null) {
389 //Slog.v(TAG, "Creating new array for " + name);
390 array = new ArrayList<F>();
391 mBaseTypeToFilter.put(baseName, array);
392 }
393 array.add(filter);
394 } else {
395 array = mWildTypeToFilter.get(baseName);
396 if (array == null) {
397 //Slog.v(TAG, "Creating new array for " + name);
398 array = new ArrayList<F>();
399 mWildTypeToFilter.put(baseName, array);
400 }
401 array.add(filter);
402 }
403 }
404
405 return num;
406 }
407
408 private final int unregister_mime_types(F filter, String prefix) {
409 final Iterator<String> i = filter.typesIterator();
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) Slog.v(TAG, prefix + name);
419 String baseName = name;
420 final int slashpos = name.indexOf('/');
421 if (slashpos > 0) {
422 baseName = name.substring(0, slashpos).intern();
423 } else {
424 name = name + "/*";
425 }
426
427 if (!remove_all_objects(mTypeToFilter.get(name), filter)) {
428 mTypeToFilter.remove(name);
429 }
430
431 if (slashpos > 0) {
432 if (!remove_all_objects(mBaseTypeToFilter.get(baseName), filter)) {
433 mBaseTypeToFilter.remove(baseName);
434 }
435 } else {
436 if (!remove_all_objects(mWildTypeToFilter.get(baseName), filter)) {
437 mWildTypeToFilter.remove(baseName);
438 }
439 }
440 }
441 return num;
442 }
443
444 private final int register_intent_filter(F filter, Iterator<String> i,
445 HashMap<String, ArrayList<F>> dest, String prefix) {
446 if (i == null) {
447 return 0;
448 }
449
450 int num = 0;
451 while (i.hasNext()) {
452 String name = i.next();
453 num++;
454 if (localLOGV) Slog.v(TAG, prefix + name);
455 ArrayList<F> array = dest.get(name);
456 if (array == null) {
457 //Slog.v(TAG, "Creating new array for " + name);
458 array = new ArrayList<F>();
459 dest.put(name, array);
460 }
461 array.add(filter);
462 }
463 return num;
464 }
465
466 private final int unregister_intent_filter(F filter, Iterator<String> i,
467 HashMap<String, ArrayList<F>> dest, String prefix) {
468 if (i == null) {
469 return 0;
470 }
471
472 int num = 0;
473 while (i.hasNext()) {
474 String name = i.next();
475 num++;
476 if (localLOGV) Slog.v(TAG, prefix + name);
477 if (!remove_all_objects(dest.get(name), filter)) {
478 dest.remove(name);
479 }
480 }
481 return num;
482 }
483
484 private final boolean remove_all_objects(List<F> list, Object object) {
485 if (list != null) {
486 int N = list.size();
487 for (int idx=0; idx<N; idx++) {
488 if (list.get(idx) == object) {
489 list.remove(idx);
490 idx--;
491 N--;
492 }
493 }
494 return N > 0;
495 }
496 return false;
497 }
498
499 private static FastImmutableArraySet<String> getFastIntentCategories(Intent intent) {
500 final Set<String> categories = intent.getCategories();
501 if (categories == null) {
502 return null;
503 }
504 return new FastImmutableArraySet<String>(categories.toArray(new String[categories.size()]));
505 }
506
507 private void buildResolveList(Intent intent, FastImmutableArraySet<String> categories,
508 boolean debug, boolean defaultOnly,
509 String resolvedType, String scheme, List<F> src, List<R> dest, int userId) {
510 final String action = intent.getAction();
511 final Uri data = intent.getData();
512 final String packageName = intent.getPackage();
513
514 final boolean excludingStopped = intent.isExcludingStopped();
515
516 final int N = src != null ? src.size() : 0;
517 boolean hasNonDefaults = false;
518 int i;
519 for (i=0; i<N; i++) {
520 F filter = src.get(i);
521 int match;
522 if (debug) Slog.v(TAG, "Matching against filter " + filter);
523
524 if (excludingStopped && isFilterStopped(filter, userId)) {
525 if (debug) {
526 Slog.v(TAG, " Filter's target is stopped; skipping");
527 }
528 continue;
529 }
530
531 // Is delivery being limited to filters owned by a particular package?
Ben Gruver4efe9402013-04-02 21:18:41 -0700532 if (packageName != null && !isPackageForFilter(packageName, filter)) {
Dianne Hackborn9ec6cdd2012-05-31 10:57:54 -0700533 if (debug) {
534 Slog.v(TAG, " Filter is not from package " + packageName + "; skipping");
535 }
536 continue;
537 }
538
539 // Do we already have this one?
540 if (!allowFilterResult(filter, dest)) {
541 if (debug) {
542 Slog.v(TAG, " Filter's target already added");
543 }
544 continue;
545 }
546
547 match = filter.match(action, resolvedType, scheme, data, categories, TAG);
548 if (match >= 0) {
549 if (debug) Slog.v(TAG, " Filter matched! match=0x" +
550 Integer.toHexString(match));
551 if (!defaultOnly || filter.hasCategory(Intent.CATEGORY_DEFAULT)) {
552 final R oneResult = newResult(filter, match, userId);
553 if (oneResult != null) {
554 dest.add(oneResult);
555 }
556 } else {
557 hasNonDefaults = true;
558 }
559 } else {
560 if (debug) {
561 String reason;
562 switch (match) {
563 case IntentFilter.NO_MATCH_ACTION: reason = "action"; break;
564 case IntentFilter.NO_MATCH_CATEGORY: reason = "category"; break;
565 case IntentFilter.NO_MATCH_DATA: reason = "data"; break;
566 case IntentFilter.NO_MATCH_TYPE: reason = "type"; break;
567 default: reason = "unknown reason"; break;
568 }
569 Slog.v(TAG, " Filter did not match: " + reason);
570 }
571 }
572 }
573
574 if (dest.size() == 0 && hasNonDefaults) {
575 Slog.w(TAG, "resolveIntent failed: found match, but none with Intent.CATEGORY_DEFAULT");
576 }
577 }
578
579 // Sorts a List of IntentFilter objects into descending priority order.
580 @SuppressWarnings("rawtypes")
581 private static final Comparator mResolvePrioritySorter = new Comparator() {
582 public int compare(Object o1, Object o2) {
583 final int q1 = ((IntentFilter) o1).getPriority();
584 final int q2 = ((IntentFilter) o2).getPriority();
585 return (q1 > q2) ? -1 : ((q1 < q2) ? 1 : 0);
586 }
587 };
588
589 /**
590 * All filters that have been registered.
591 */
592 final HashSet<F> mFilters = new HashSet<F>();
593
594 /**
595 * All of the MIME types that have been registered, such as "image/jpeg",
596 * "image/*", or "{@literal *}/*".
597 */
598 final HashMap<String, ArrayList<F>> mTypeToFilter
599 = new HashMap<String, ArrayList<F>>();
600
601 /**
602 * The base names of all of all fully qualified MIME types that have been
603 * registered, such as "image" or "*". Wild card MIME types such as
604 * "image/*" will not be here.
605 */
606 final HashMap<String, ArrayList<F>> mBaseTypeToFilter
607 = new HashMap<String, ArrayList<F>>();
608
609 /**
610 * The base names of all of the MIME types with a sub-type wildcard that
611 * have been registered. For example, a filter with "image/*" will be
612 * included here as "image" but one with "image/jpeg" will not be
613 * included here. This also includes the "*" for the "{@literal *}/*"
614 * MIME type.
615 */
616 final HashMap<String, ArrayList<F>> mWildTypeToFilter
617 = new HashMap<String, ArrayList<F>>();
618
619 /**
620 * All of the URI schemes (such as http) that have been registered.
621 */
622 final HashMap<String, ArrayList<F>> mSchemeToFilter
623 = new HashMap<String, ArrayList<F>>();
624
625 /**
626 * All of the actions that have been registered, but only those that did
627 * not specify data.
628 */
629 final HashMap<String, ArrayList<F>> mActionToFilter
630 = new HashMap<String, ArrayList<F>>();
631
632 /**
633 * All of the actions that have been registered and specified a MIME type.
634 */
635 final HashMap<String, ArrayList<F>> mTypedActionToFilter
636 = new HashMap<String, ArrayList<F>>();
637}
638