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