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