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