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