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