IntentResolver.java revision 2c376fc46cd01b12e003a7bf83d82f527f6efaf1
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.util.Config;
39import android.content.ContentResolver;
40import android.content.Intent;
41import android.content.IntentFilter;
42
43/**
44 * {@hide}
45 */
46public 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 || Config.LOGV;
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, ArrayList<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, ArrayList<F>> e : map.entrySet()) {
105            ArrayList<F> a = e.getValue();
106            final int N = a.size();
107            boolean printedHeader = false;
108            for (int i=0; i<N; i++) {
109                F filter = a.get(i);
110                if (packageName != null && !packageName.equals(packageForFilter(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<ArrayList<F>> listCut) {
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);
218        }
219        sortResults(resultList);
220        return resultList;
221    }
222
223    public List<R> queryIntent(Intent intent, String resolvedType, boolean defaultOnly) {
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);
296        }
297        if (secondTypeCut != null) {
298            buildResolveList(intent, categories, debug, defaultOnly,
299                    resolvedType, scheme, secondTypeCut, finalList);
300        }
301        if (thirdTypeCut != null) {
302            buildResolveList(intent, categories, debug, defaultOnly,
303                    resolvedType, scheme, thirdTypeCut, finalList);
304        }
305        if (schemeCut != null) {
306            buildResolveList(intent, categories, debug, defaultOnly,
307                    resolvedType, scheme, schemeCut, finalList);
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    protected String packageForFilter(F filter) {
330        return null;
331    }
332
333    protected R newResult(F filter, int match) {
334        return (R)filter;
335    }
336
337    protected void sortResults(List<R> results) {
338        Collections.sort(results, mResolvePrioritySorter);
339    }
340
341    protected void dumpFilter(PrintWriter out, String prefix, F filter) {
342        out.print(prefix); out.println(filter);
343    }
344
345    private final int register_mime_types(F filter, String prefix) {
346        final Iterator<String> i = filter.typesIterator();
347        if (i == null) {
348            return 0;
349        }
350
351        int num = 0;
352        while (i.hasNext()) {
353            String name = i.next();
354            num++;
355            if (localLOGV) Slog.v(TAG, prefix + name);
356            String baseName = name;
357            final int slashpos = name.indexOf('/');
358            if (slashpos > 0) {
359                baseName = name.substring(0, slashpos).intern();
360            } else {
361                name = name + "/*";
362            }
363
364            ArrayList<F> array = mTypeToFilter.get(name);
365            if (array == null) {
366                //Slog.v(TAG, "Creating new array for " + name);
367                array = new ArrayList<F>();
368                mTypeToFilter.put(name, array);
369            }
370            array.add(filter);
371
372            if (slashpos > 0) {
373                array = mBaseTypeToFilter.get(baseName);
374                if (array == null) {
375                    //Slog.v(TAG, "Creating new array for " + name);
376                    array = new ArrayList<F>();
377                    mBaseTypeToFilter.put(baseName, array);
378                }
379                array.add(filter);
380            } else {
381                array = mWildTypeToFilter.get(baseName);
382                if (array == null) {
383                    //Slog.v(TAG, "Creating new array for " + name);
384                    array = new ArrayList<F>();
385                    mWildTypeToFilter.put(baseName, array);
386                }
387                array.add(filter);
388            }
389        }
390
391        return num;
392    }
393
394    private final int unregister_mime_types(F filter, String prefix) {
395        final Iterator<String> i = filter.typesIterator();
396        if (i == null) {
397            return 0;
398        }
399
400        int num = 0;
401        while (i.hasNext()) {
402            String name = i.next();
403            num++;
404            if (localLOGV) Slog.v(TAG, prefix + name);
405            String baseName = name;
406            final int slashpos = name.indexOf('/');
407            if (slashpos > 0) {
408                baseName = name.substring(0, slashpos).intern();
409            } else {
410                name = name + "/*";
411            }
412
413            if (!remove_all_objects(mTypeToFilter.get(name), filter)) {
414                mTypeToFilter.remove(name);
415            }
416
417            if (slashpos > 0) {
418                if (!remove_all_objects(mBaseTypeToFilter.get(baseName), filter)) {
419                    mBaseTypeToFilter.remove(baseName);
420                }
421            } else {
422                if (!remove_all_objects(mWildTypeToFilter.get(baseName), filter)) {
423                    mWildTypeToFilter.remove(baseName);
424                }
425            }
426        }
427        return num;
428    }
429
430    private final int register_intent_filter(F filter, Iterator<String> i,
431            HashMap<String, ArrayList<F>> dest, String prefix) {
432        if (i == null) {
433            return 0;
434        }
435
436        int num = 0;
437        while (i.hasNext()) {
438            String name = i.next();
439            num++;
440            if (localLOGV) Slog.v(TAG, prefix + name);
441            ArrayList<F> array = dest.get(name);
442            if (array == null) {
443                //Slog.v(TAG, "Creating new array for " + name);
444                array = new ArrayList<F>();
445                dest.put(name, array);
446            }
447            array.add(filter);
448        }
449        return num;
450    }
451
452    private final int unregister_intent_filter(F filter, Iterator<String> i,
453            HashMap<String, ArrayList<F>> dest, String prefix) {
454        if (i == null) {
455            return 0;
456        }
457
458        int num = 0;
459        while (i.hasNext()) {
460            String name = i.next();
461            num++;
462            if (localLOGV) Slog.v(TAG, prefix + name);
463            if (!remove_all_objects(dest.get(name), filter)) {
464                dest.remove(name);
465            }
466        }
467        return num;
468    }
469
470    private final boolean remove_all_objects(List<F> list, Object object) {
471        if (list != null) {
472            int N = list.size();
473            for (int idx=0; idx<N; idx++) {
474                if (list.get(idx) == object) {
475                    list.remove(idx);
476                    idx--;
477                    N--;
478                }
479            }
480            return N > 0;
481        }
482        return false;
483    }
484
485    private static FastImmutableArraySet<String> getFastIntentCategories(Intent intent) {
486        final Set<String> categories = intent.getCategories();
487        if (categories == null) {
488            return null;
489        }
490        return new FastImmutableArraySet<String>(categories.toArray(new String[categories.size()]));
491    }
492
493    private void buildResolveList(Intent intent, FastImmutableArraySet<String> categories,
494            boolean debug, boolean defaultOnly,
495            String resolvedType, String scheme, List<F> src, List<R> dest) {
496        final String action = intent.getAction();
497        final Uri data = intent.getData();
498
499        final int N = src != null ? src.size() : 0;
500        boolean hasNonDefaults = false;
501        int i;
502        for (i=0; i<N; i++) {
503            F filter = src.get(i);
504            int match;
505            if (debug) Slog.v(TAG, "Matching against filter " + filter);
506
507            // Do we already have this one?
508            if (!allowFilterResult(filter, dest)) {
509                if (debug) {
510                    Slog.v(TAG, "  Filter's target already added");
511                }
512                continue;
513            }
514
515            match = filter.match(action, resolvedType, scheme, data, categories, TAG);
516            if (match >= 0) {
517                if (debug) Slog.v(TAG, "  Filter matched!  match=0x" +
518                        Integer.toHexString(match));
519                if (!defaultOnly || filter.hasCategory(Intent.CATEGORY_DEFAULT)) {
520                    final R oneResult = newResult(filter, match);
521                    if (oneResult != null) {
522                        dest.add(oneResult);
523                    }
524                } else {
525                    hasNonDefaults = true;
526                }
527            } else {
528                if (debug) {
529                    String reason;
530                    switch (match) {
531                        case IntentFilter.NO_MATCH_ACTION: reason = "action"; break;
532                        case IntentFilter.NO_MATCH_CATEGORY: reason = "category"; break;
533                        case IntentFilter.NO_MATCH_DATA: reason = "data"; break;
534                        case IntentFilter.NO_MATCH_TYPE: reason = "type"; break;
535                        default: reason = "unknown reason"; break;
536                    }
537                    Slog.v(TAG, "  Filter did not match: " + reason);
538                }
539            }
540        }
541
542        if (dest.size() == 0 && hasNonDefaults) {
543            Slog.w(TAG, "resolveIntent failed: found match, but none with Intent.CATEGORY_DEFAULT");
544        }
545    }
546
547    // Sorts a List of IntentFilter objects into descending priority order.
548    private static final Comparator mResolvePrioritySorter = new Comparator() {
549        public int compare(Object o1, Object o2) {
550            final int q1 = ((IntentFilter) o1).getPriority();
551            final int q2 = ((IntentFilter) o2).getPriority();
552            return (q1 > q2) ? -1 : ((q1 < q2) ? 1 : 0);
553        }
554    };
555
556    /**
557     * All filters that have been registered.
558     */
559    private final HashSet<F> mFilters = new HashSet<F>();
560
561    /**
562     * All of the MIME types that have been registered, such as "image/jpeg",
563     * "image/*", or "{@literal *}/*".
564     */
565    private final HashMap<String, ArrayList<F>> mTypeToFilter
566            = new HashMap<String, ArrayList<F>>();
567
568    /**
569     * The base names of all of all fully qualified MIME types that have been
570     * registered, such as "image" or "*".  Wild card MIME types such as
571     * "image/*" will not be here.
572     */
573    private final HashMap<String, ArrayList<F>> mBaseTypeToFilter
574            = new HashMap<String, ArrayList<F>>();
575
576    /**
577     * The base names of all of the MIME types with a sub-type wildcard that
578     * have been registered.  For example, a filter with "image/*" will be
579     * included here as "image" but one with "image/jpeg" will not be
580     * included here.  This also includes the "*" for the "{@literal *}/*"
581     * MIME type.
582     */
583    private final HashMap<String, ArrayList<F>> mWildTypeToFilter
584            = new HashMap<String, ArrayList<F>>();
585
586    /**
587     * All of the URI schemes (such as http) that have been registered.
588     */
589    private final HashMap<String, ArrayList<F>> mSchemeToFilter
590            = new HashMap<String, ArrayList<F>>();
591
592    /**
593     * All of the actions that have been registered, but only those that did
594     * not specify data.
595     */
596    private final HashMap<String, ArrayList<F>> mActionToFilter
597            = new HashMap<String, ArrayList<F>>();
598
599    /**
600     * All of the actions that have been registered and specified a MIME type.
601     */
602    private final HashMap<String, ArrayList<F>> mTypedActionToFilter
603            = new HashMap<String, ArrayList<F>>();
604}
605
606