IntentResolver.java revision 54b6cfa9a9e5b861a9930af873580d6dc20f773c
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.util.ArrayList;
20import java.util.Collections;
21import java.util.Comparator;
22import java.util.HashMap;
23import java.util.HashSet;
24import java.util.Iterator;
25import java.util.List;
26import java.util.Map;
27import java.util.Set;
28
29import android.util.Log;
30import android.util.LogPrinter;
31import android.util.Printer;
32
33import android.util.Config;
34import android.content.ContentResolver;
35import android.content.Intent;
36import android.content.IntentFilter;
37
38/**
39 * {@hide}
40 */
41public class IntentResolver<F extends IntentFilter, R extends Object> {
42    final private static String TAG = "IntentResolver";
43    final private static boolean DEBUG = false;
44    final private static boolean localLOGV = DEBUG || Config.LOGV;
45
46    public void addFilter(F f) {
47        if (localLOGV) {
48            Log.v(TAG, "Adding filter: " + f);
49            f.dump(new LogPrinter(Log.VERBOSE, TAG), "      ");
50            Log.v(TAG, "    Building Lookup Maps:");
51        }
52
53        mFilters.add(f);
54        int numS = register_intent_filter(f, f.schemesIterator(),
55                mSchemeToFilter, "      Scheme: ");
56        int numT = register_mime_types(f, "      Type: ");
57        if (numS == 0 && numT == 0) {
58            register_intent_filter(f, f.actionsIterator(),
59                    mActionToFilter, "      Action: ");
60        }
61        if (numT != 0) {
62            register_intent_filter(f, f.actionsIterator(),
63                    mTypedActionToFilter, "      TypedAction: ");
64        }
65    }
66
67    public void removeFilter(F f) {
68        removeFilterInternal(f);
69        mFilters.remove(f);
70    }
71
72    void removeFilterInternal(F f) {
73        if (localLOGV) {
74            Log.v(TAG, "Removing filter: " + f);
75            f.dump(new LogPrinter(Log.VERBOSE, TAG), "      ");
76            Log.v(TAG, "    Cleaning Lookup Maps:");
77        }
78
79        int numS = unregister_intent_filter(f, f.schemesIterator(),
80                mSchemeToFilter, "      Scheme: ");
81        int numT = unregister_mime_types(f, "      Type: ");
82        if (numS == 0 && numT == 0) {
83            unregister_intent_filter(f, f.actionsIterator(),
84                    mActionToFilter, "      Action: ");
85        }
86        if (numT != 0) {
87            unregister_intent_filter(f, f.actionsIterator(),
88                    mTypedActionToFilter, "      TypedAction: ");
89        }
90    }
91
92    void dumpMap(Printer out, String prefix, Map<String, ArrayList<F>> map) {
93        String eprefix = prefix + "  ";
94        String fprefix = prefix + "    ";
95        for (Map.Entry<String, ArrayList<F>> e : map.entrySet()) {
96            out.println(eprefix + e.getKey() + ":");
97            ArrayList<F> a = e.getValue();
98            final int N = a.size();
99            for (int i=0; i<N; i++) {
100                dumpFilter(out, fprefix, a.get(i));
101            }
102        }
103    }
104
105    public void dump(Printer out, String prefix) {
106        out.println(prefix + "Full MIME Types:");
107        dumpMap(out, prefix+"  ", mTypeToFilter);
108        out.println(prefix);
109        out.println(prefix + "Base MIME Types:");
110        dumpMap(out, prefix+"  ", mBaseTypeToFilter);
111        out.println(prefix);
112        out.println(prefix + "Wild MIME Types:");
113        dumpMap(out, prefix+"  ", mWildTypeToFilter);
114        out.println(prefix);
115        out.println(prefix + "Schemes:");
116        dumpMap(out, prefix+"  ", mSchemeToFilter);
117        out.println(prefix);
118        out.println(prefix + "Non-Data Actions:");
119        dumpMap(out, prefix+"  ", mActionToFilter);
120        out.println(prefix);
121        out.println(prefix + "MIME Typed Actions:");
122        dumpMap(out, prefix+"  ", mTypedActionToFilter);
123    }
124
125    private class IteratorWrapper implements Iterator<F> {
126        private final Iterator<F> mI;
127        private F mCur;
128
129        IteratorWrapper(Iterator<F> it) {
130            mI = it;
131        }
132
133        public boolean hasNext() {
134            return mI.hasNext();
135        }
136
137        public F next() {
138            return (mCur = mI.next());
139        }
140
141        public void remove() {
142            if (mCur != null) {
143                removeFilterInternal(mCur);
144            }
145            mI.remove();
146        }
147
148    }
149
150    /**
151     * Returns an iterator allowing filters to be removed.
152     */
153    public Iterator<F> filterIterator() {
154        return new IteratorWrapper(mFilters.iterator());
155    }
156
157    /**
158     * Returns a read-only set of the filters.
159     */
160    public Set<F> filterSet() {
161        return Collections.unmodifiableSet(mFilters);
162    }
163
164    public List<R> queryIntent(ContentResolver resolver, Intent intent,
165            String resolvedType, boolean defaultOnly) {
166        String scheme = intent.getScheme();
167
168        ArrayList<R> finalList = new ArrayList<R>();
169
170        final boolean debug = localLOGV ||
171                ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
172
173        if (debug) Log.v(
174            TAG, "Resolving type " + resolvedType + " scheme " + scheme
175            + " of intent " + intent);
176
177        ArrayList<F> firstTypeCut = null;
178        ArrayList<F> secondTypeCut = null;
179        ArrayList<F> thirdTypeCut = null;
180        ArrayList<F> schemeCut = null;
181
182        // If the intent includes a MIME type, then we want to collect all of
183        // the filters that match that MIME type.
184        if (resolvedType != null) {
185            int slashpos = resolvedType.indexOf('/');
186            if (slashpos > 0) {
187                final String baseType = resolvedType.substring(0, slashpos);
188                if (!baseType.equals("*")) {
189                    if (resolvedType.length() != slashpos+2
190                            || resolvedType.charAt(slashpos+1) != '*') {
191                        // Not a wild card, so we can just look for all filters that
192                        // completely match or wildcards whose base type matches.
193                        firstTypeCut = mTypeToFilter.get(resolvedType);
194                        if (debug) Log.v(TAG, "First type cut: " + firstTypeCut);
195                        secondTypeCut = mWildTypeToFilter.get(baseType);
196                        if (debug) Log.v(TAG, "Second type cut: " + secondTypeCut);
197                    } else {
198                        // We can match anything with our base type.
199                        firstTypeCut = mBaseTypeToFilter.get(baseType);
200                        if (debug) Log.v(TAG, "First type cut: " + firstTypeCut);
201                        secondTypeCut = mWildTypeToFilter.get(baseType);
202                        if (debug) Log.v(TAG, "Second type cut: " + secondTypeCut);
203                    }
204                    // Any */* types always apply, but we only need to do this
205                    // if the intent type was not already */*.
206                    thirdTypeCut = mWildTypeToFilter.get("*");
207                    if (debug) Log.v(TAG, "Third type cut: " + thirdTypeCut);
208                } else if (intent.getAction() != null) {
209                    // The intent specified any type ({@literal *}/*).  This
210                    // can be a whole heck of a lot of things, so as a first
211                    // cut let's use the action instead.
212                    firstTypeCut = mTypedActionToFilter.get(intent.getAction());
213                    if (debug) Log.v(TAG, "Typed Action list: " + firstTypeCut);
214                }
215            }
216        }
217
218        // If the intent includes a data URI, then we want to collect all of
219        // the filters that match its scheme (we will further refine matches
220        // on the authority and path by directly matching each resulting filter).
221        if (scheme != null) {
222            schemeCut = mSchemeToFilter.get(scheme);
223            if (debug) Log.v(TAG, "Scheme list: " + schemeCut);
224        }
225
226        // If the intent does not specify any data -- either a MIME type or
227        // a URI -- then we will only be looking for matches against empty
228        // data.
229        if (resolvedType == null && scheme == null && intent.getAction() != null) {
230            firstTypeCut = mActionToFilter.get(intent.getAction());
231            if (debug) Log.v(TAG, "Action list: " + firstTypeCut);
232        }
233
234        if (firstTypeCut != null) {
235            buildResolveList(intent, debug, defaultOnly,
236                    resolvedType, scheme, firstTypeCut, finalList);
237        }
238        if (secondTypeCut != null) {
239            buildResolveList(intent, debug, defaultOnly,
240                    resolvedType, scheme, secondTypeCut, finalList);
241        }
242        if (thirdTypeCut != null) {
243            buildResolveList(intent, debug, defaultOnly,
244                    resolvedType, scheme, thirdTypeCut, finalList);
245        }
246        if (schemeCut != null) {
247            buildResolveList(intent, debug, defaultOnly,
248                    resolvedType, scheme, schemeCut, finalList);
249        }
250        sortResults(finalList);
251
252        if (debug) {
253            Log.v(TAG, "Final result list:");
254            for (R r : finalList) {
255                Log.v(TAG, "  " + r);
256            }
257        }
258        return finalList;
259    }
260
261    /**
262     * Control whether the given filter is allowed to go into the result
263     * list.  Mainly intended to prevent adding multiple filters for the
264     * same target object.
265     */
266    protected boolean allowFilterResult(F filter, List<R> dest) {
267        return true;
268    }
269
270    protected R newResult(F filter, int match) {
271        return (R)filter;
272    }
273
274    protected void sortResults(List<R> results) {
275        Collections.sort(results, mResolvePrioritySorter);
276    }
277
278    protected void dumpFilter(Printer out, String prefix, F filter) {
279        out.println(prefix + filter);
280    }
281
282    private final int register_mime_types(F filter, String prefix) {
283        final Iterator<String> i = filter.typesIterator();
284        if (i == null) {
285            return 0;
286        }
287
288        int num = 0;
289        while (i.hasNext()) {
290            String name = (String)i.next();
291            num++;
292            if (localLOGV) Log.v(TAG, prefix + name);
293            String baseName = name;
294            final int slashpos = name.indexOf('/');
295            if (slashpos > 0) {
296                baseName = name.substring(0, slashpos).intern();
297            } else {
298                name = name + "/*";
299            }
300
301            ArrayList<F> array = mTypeToFilter.get(name);
302            if (array == null) {
303                //Log.v(TAG, "Creating new array for " + name);
304                array = new ArrayList<F>();
305                mTypeToFilter.put(name, array);
306            }
307            array.add(filter);
308
309            if (slashpos > 0) {
310                array = mBaseTypeToFilter.get(baseName);
311                if (array == null) {
312                    //Log.v(TAG, "Creating new array for " + name);
313                    array = new ArrayList<F>();
314                    mBaseTypeToFilter.put(baseName, array);
315                }
316                array.add(filter);
317            } else {
318                array = mWildTypeToFilter.get(baseName);
319                if (array == null) {
320                    //Log.v(TAG, "Creating new array for " + name);
321                    array = new ArrayList<F>();
322                    mWildTypeToFilter.put(baseName, array);
323                }
324                array.add(filter);
325            }
326        }
327
328        return num;
329    }
330
331    private final int unregister_mime_types(F filter, String prefix) {
332        final Iterator<String> i = filter.typesIterator();
333        if (i == null) {
334            return 0;
335        }
336
337        int num = 0;
338        while (i.hasNext()) {
339            String name = (String)i.next();
340            num++;
341            if (localLOGV) Log.v(TAG, prefix + name);
342            String baseName = name;
343            final int slashpos = name.indexOf('/');
344            if (slashpos > 0) {
345                baseName = name.substring(0, slashpos).intern();
346            } else {
347                name = name + "/*";
348            }
349
350            if (!remove_all_objects(mTypeToFilter.get(name), filter)) {
351                mTypeToFilter.remove(name);
352            }
353
354            if (slashpos > 0) {
355                if (!remove_all_objects(mBaseTypeToFilter.get(baseName), filter)) {
356                    mBaseTypeToFilter.remove(baseName);
357                }
358            } else {
359                if (!remove_all_objects(mWildTypeToFilter.get(baseName), filter)) {
360                    mWildTypeToFilter.remove(baseName);
361                }
362            }
363        }
364        return num;
365    }
366
367    private final int register_intent_filter(F filter, Iterator<String> i,
368            HashMap<String, ArrayList<F>> dest, String prefix) {
369        if (i == null) {
370            return 0;
371        }
372
373        int num = 0;
374        while (i.hasNext()) {
375            String name = i.next();
376            num++;
377            if (localLOGV) Log.v(TAG, prefix + name);
378            ArrayList<F> array = dest.get(name);
379            if (array == null) {
380                //Log.v(TAG, "Creating new array for " + name);
381                array = new ArrayList<F>();
382                dest.put(name, array);
383            }
384            array.add(filter);
385        }
386        return num;
387    }
388
389    private final int unregister_intent_filter(F filter, Iterator<String> i,
390            HashMap<String, ArrayList<F>> dest, String prefix) {
391        if (i == null) {
392            return 0;
393        }
394
395        int num = 0;
396        while (i.hasNext()) {
397            String name = i.next();
398            num++;
399            if (localLOGV) Log.v(TAG, prefix + name);
400            if (!remove_all_objects(dest.get(name), filter)) {
401                dest.remove(name);
402            }
403        }
404        return num;
405    }
406
407    private final boolean remove_all_objects(List<F> list, Object object) {
408        if (list != null) {
409            int N = list.size();
410            for (int idx=0; idx<N; idx++) {
411                if (list.get(idx) == object) {
412                    list.remove(idx);
413                    idx--;
414                    N--;
415                }
416            }
417            return N > 0;
418        }
419        return false;
420    }
421
422    private void buildResolveList(Intent intent, boolean debug, boolean defaultOnly,
423            String resolvedType, String scheme, List<F> src, List<R> dest) {
424        Set<String> categories = intent.getCategories();
425
426        final int N = src != null ? src.size() : 0;
427        boolean hasNonDefaults = false;
428        int i;
429        for (i=0; i<N; i++) {
430            F filter = src.get(i);
431            int match;
432            if (debug) Log.v(TAG, "Matching against filter " + filter);
433
434            // Do we already have this one?
435            if (!allowFilterResult(filter, dest)) {
436                if (debug) {
437                    Log.v(TAG, "  Filter's target already added");
438                }
439                continue;
440            }
441
442            match = filter.match(
443                    intent.getAction(), resolvedType, scheme, intent.getData(), categories, TAG);
444            if (match >= 0) {
445                if (debug) Log.v(TAG, "  Filter matched!  match=0x" +
446                        Integer.toHexString(match));
447                if (!defaultOnly || filter.hasCategory(Intent.CATEGORY_DEFAULT)) {
448                    final R oneResult = newResult(filter, match);
449                    if (oneResult != null) {
450                        dest.add(oneResult);
451                    }
452                } else {
453                    hasNonDefaults = true;
454                }
455            } else {
456                if (debug) {
457                    String reason;
458                    switch (match) {
459                        case IntentFilter.NO_MATCH_ACTION: reason = "action"; break;
460                        case IntentFilter.NO_MATCH_CATEGORY: reason = "category"; break;
461                        case IntentFilter.NO_MATCH_DATA: reason = "data"; break;
462                        case IntentFilter.NO_MATCH_TYPE: reason = "type"; break;
463                        default: reason = "unknown reason"; break;
464                    }
465                    Log.v(TAG, "  Filter did not match: " + reason);
466                }
467            }
468        }
469
470        if (dest.size() == 0 && hasNonDefaults) {
471            Log.w(TAG, "resolveIntent failed: found match, but none with Intent.CATEGORY_DEFAULT");
472        }
473    }
474
475    // Sorts a List of IntentFilter objects into descending priority order.
476    private static final Comparator mResolvePrioritySorter = new Comparator() {
477        public int compare(Object o1, Object o2) {
478            float q1 = ((IntentFilter)o1).getPriority();
479            float q2 = ((IntentFilter)o2).getPriority();
480            return (q1 > q2) ? -1 : ((q1 < q2) ? 1 : 0);
481        }
482    };
483
484    /**
485     * All filters that have been registered.
486     */
487    private final HashSet<F> mFilters = new HashSet<F>();
488
489    /**
490     * All of the MIME types that have been registered, such as "image/jpeg",
491     * "image/*", or "{@literal *}/*".
492     */
493    private final HashMap<String, ArrayList<F>> mTypeToFilter
494            = new HashMap<String, ArrayList<F>>();
495
496    /**
497     * The base names of all of all fully qualified MIME types that have been
498     * registered, such as "image" or "*".  Wild card MIME types such as
499     * "image/*" will not be here.
500     */
501    private final HashMap<String, ArrayList<F>> mBaseTypeToFilter
502            = new HashMap<String, ArrayList<F>>();
503
504    /**
505     * The base names of all of the MIME types with a sub-type wildcard that
506     * have been registered.  For example, a filter with "image/*" will be
507     * included here as "image" but one with "image/jpeg" will not be
508     * included here.  This also includes the "*" for the "{@literal *}/*"
509     * MIME type.
510     */
511    private final HashMap<String, ArrayList<F>> mWildTypeToFilter
512            = new HashMap<String, ArrayList<F>>();
513
514    /**
515     * All of the URI schemes (such as http) that have been registered.
516     */
517    private final HashMap<String, ArrayList<F>> mSchemeToFilter
518            = new HashMap<String, ArrayList<F>>();
519
520    /**
521     * All of the actions that have been registered, but only those that did
522     * not specify data.
523     */
524    private final HashMap<String, ArrayList<F>> mActionToFilter
525            = new HashMap<String, ArrayList<F>>();
526
527    /**
528     * All of the actions that have been registered and specified a MIME type.
529     */
530    private final HashMap<String, ArrayList<F>> mTypedActionToFilter
531            = new HashMap<String, ArrayList<F>>();
532}
533
534