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