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