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