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