summaryrefslogtreecommitdiffstats
path: root/services/core/java/com/android/server/IntentResolver.java
diff options
context:
space:
mode:
Diffstat (limited to 'services/core/java/com/android/server/IntentResolver.java')
-rw-r--r--services/core/java/com/android/server/IntentResolver.java660
1 files changed, 660 insertions, 0 deletions
diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java
new file mode 100644
index 0000000..64b0487
--- /dev/null
+++ b/services/core/java/com/android/server/IntentResolver.java
@@ -0,0 +1,660 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import android.net.Uri;
+import android.util.FastImmutableArraySet;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.PrintWriterPrinter;
+import android.util.Slog;
+import android.util.LogPrinter;
+import android.util.Printer;
+
+import android.content.Intent;
+import android.content.IntentFilter;
+import com.android.internal.util.FastPrintWriter;
+
+/**
+ * {@hide}
+ */
+public abstract class IntentResolver<F extends IntentFilter, R extends Object> {
+ final private static String TAG = "IntentResolver";
+ final private static boolean DEBUG = false;
+ final private static boolean localLOGV = DEBUG || false;
+
+ public void addFilter(F f) {
+ if (localLOGV) {
+ Slog.v(TAG, "Adding filter: " + f);
+ f.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
+ Slog.v(TAG, " Building Lookup Maps:");
+ }
+
+ mFilters.add(f);
+ int numS = register_intent_filter(f, f.schemesIterator(),
+ mSchemeToFilter, " Scheme: ");
+ int numT = register_mime_types(f, " Type: ");
+ if (numS == 0 && numT == 0) {
+ register_intent_filter(f, f.actionsIterator(),
+ mActionToFilter, " Action: ");
+ }
+ if (numT != 0) {
+ register_intent_filter(f, f.actionsIterator(),
+ mTypedActionToFilter, " TypedAction: ");
+ }
+ }
+
+ public void removeFilter(F f) {
+ removeFilterInternal(f);
+ mFilters.remove(f);
+ }
+
+ void removeFilterInternal(F f) {
+ if (localLOGV) {
+ Slog.v(TAG, "Removing filter: " + f);
+ f.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
+ Slog.v(TAG, " Cleaning Lookup Maps:");
+ }
+
+ int numS = unregister_intent_filter(f, f.schemesIterator(),
+ mSchemeToFilter, " Scheme: ");
+ int numT = unregister_mime_types(f, " Type: ");
+ if (numS == 0 && numT == 0) {
+ unregister_intent_filter(f, f.actionsIterator(),
+ mActionToFilter, " Action: ");
+ }
+ if (numT != 0) {
+ unregister_intent_filter(f, f.actionsIterator(),
+ mTypedActionToFilter, " TypedAction: ");
+ }
+ }
+
+ boolean dumpMap(PrintWriter out, String titlePrefix, String title,
+ String prefix, Map<String, F[]> map, String packageName,
+ boolean printFilter) {
+ String eprefix = prefix + " ";
+ String fprefix = prefix + " ";
+ boolean printedSomething = false;
+ Printer printer = null;
+ for (Map.Entry<String, F[]> e : map.entrySet()) {
+ F[] a = e.getValue();
+ final int N = a.length;
+ boolean printedHeader = false;
+ F filter;
+ for (int i=0; i<N && (filter=a[i]) != null; i++) {
+ if (packageName != null && !isPackageForFilter(packageName, filter)) {
+ continue;
+ }
+ if (title != null) {
+ out.print(titlePrefix); out.println(title);
+ title = null;
+ }
+ if (!printedHeader) {
+ out.print(eprefix); out.print(e.getKey()); out.println(":");
+ printedHeader = true;
+ }
+ printedSomething = true;
+ dumpFilter(out, fprefix, filter);
+ if (printFilter) {
+ if (printer == null) {
+ printer = new PrintWriterPrinter(out);
+ }
+ filter.dump(printer, fprefix + " ");
+ }
+ }
+ }
+ return printedSomething;
+ }
+
+ public boolean dump(PrintWriter out, String title, String prefix, String packageName,
+ boolean printFilter) {
+ String innerPrefix = prefix + " ";
+ String sepPrefix = "\n" + prefix;
+ String curPrefix = title + "\n" + prefix;
+ if (dumpMap(out, curPrefix, "Full MIME Types:", innerPrefix,
+ mTypeToFilter, packageName, printFilter)) {
+ curPrefix = sepPrefix;
+ }
+ if (dumpMap(out, curPrefix, "Base MIME Types:", innerPrefix,
+ mBaseTypeToFilter, packageName, printFilter)) {
+ curPrefix = sepPrefix;
+ }
+ if (dumpMap(out, curPrefix, "Wild MIME Types:", innerPrefix,
+ mWildTypeToFilter, packageName, printFilter)) {
+ curPrefix = sepPrefix;
+ }
+ if (dumpMap(out, curPrefix, "Schemes:", innerPrefix,
+ mSchemeToFilter, packageName, printFilter)) {
+ curPrefix = sepPrefix;
+ }
+ if (dumpMap(out, curPrefix, "Non-Data Actions:", innerPrefix,
+ mActionToFilter, packageName, printFilter)) {
+ curPrefix = sepPrefix;
+ }
+ if (dumpMap(out, curPrefix, "MIME Typed Actions:", innerPrefix,
+ mTypedActionToFilter, packageName, printFilter)) {
+ curPrefix = sepPrefix;
+ }
+ return curPrefix == sepPrefix;
+ }
+
+ private class IteratorWrapper implements Iterator<F> {
+ private final Iterator<F> mI;
+ private F mCur;
+
+ IteratorWrapper(Iterator<F> it) {
+ mI = it;
+ }
+
+ public boolean hasNext() {
+ return mI.hasNext();
+ }
+
+ public F next() {
+ return (mCur = mI.next());
+ }
+
+ public void remove() {
+ if (mCur != null) {
+ removeFilterInternal(mCur);
+ }
+ mI.remove();
+ }
+
+ }
+
+ /**
+ * Returns an iterator allowing filters to be removed.
+ */
+ public Iterator<F> filterIterator() {
+ return new IteratorWrapper(mFilters.iterator());
+ }
+
+ /**
+ * Returns a read-only set of the filters.
+ */
+ public Set<F> filterSet() {
+ return Collections.unmodifiableSet(mFilters);
+ }
+
+ public List<R> queryIntentFromList(Intent intent, String resolvedType,
+ boolean defaultOnly, ArrayList<F[]> listCut, int userId) {
+ ArrayList<R> resultList = new ArrayList<R>();
+
+ final boolean debug = localLOGV ||
+ ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
+
+ FastImmutableArraySet<String> categories = getFastIntentCategories(intent);
+ final String scheme = intent.getScheme();
+ int N = listCut.size();
+ for (int i = 0; i < N; ++i) {
+ buildResolveList(intent, categories, debug, defaultOnly,
+ resolvedType, scheme, listCut.get(i), resultList, userId);
+ }
+ sortResults(resultList);
+ return resultList;
+ }
+
+ public List<R> queryIntent(Intent intent, String resolvedType, boolean defaultOnly,
+ int userId) {
+ String scheme = intent.getScheme();
+
+ ArrayList<R> finalList = new ArrayList<R>();
+
+ final boolean debug = localLOGV ||
+ ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
+
+ if (debug) Slog.v(
+ TAG, "Resolving type=" + resolvedType + " scheme=" + scheme
+ + " defaultOnly=" + defaultOnly + " userId=" + userId + " of " + intent);
+
+ F[] firstTypeCut = null;
+ F[] secondTypeCut = null;
+ F[] thirdTypeCut = null;
+ F[] schemeCut = null;
+
+ // If the intent includes a MIME type, then we want to collect all of
+ // the filters that match that MIME type.
+ if (resolvedType != null) {
+ int slashpos = resolvedType.indexOf('/');
+ if (slashpos > 0) {
+ final String baseType = resolvedType.substring(0, slashpos);
+ if (!baseType.equals("*")) {
+ if (resolvedType.length() != slashpos+2
+ || resolvedType.charAt(slashpos+1) != '*') {
+ // Not a wild card, so we can just look for all filters that
+ // completely match or wildcards whose base type matches.
+ firstTypeCut = mTypeToFilter.get(resolvedType);
+ if (debug) Slog.v(TAG, "First type cut: " + Arrays.toString(firstTypeCut));
+ secondTypeCut = mWildTypeToFilter.get(baseType);
+ if (debug) Slog.v(TAG, "Second type cut: "
+ + Arrays.toString(secondTypeCut));
+ } else {
+ // We can match anything with our base type.
+ firstTypeCut = mBaseTypeToFilter.get(baseType);
+ if (debug) Slog.v(TAG, "First type cut: " + Arrays.toString(firstTypeCut));
+ secondTypeCut = mWildTypeToFilter.get(baseType);
+ if (debug) Slog.v(TAG, "Second type cut: "
+ + Arrays.toString(secondTypeCut));
+ }
+ // Any */* types always apply, but we only need to do this
+ // if the intent type was not already */*.
+ thirdTypeCut = mWildTypeToFilter.get("*");
+ if (debug) Slog.v(TAG, "Third type cut: " + Arrays.toString(thirdTypeCut));
+ } else if (intent.getAction() != null) {
+ // The intent specified any type ({@literal *}/*). This
+ // can be a whole heck of a lot of things, so as a first
+ // cut let's use the action instead.
+ firstTypeCut = mTypedActionToFilter.get(intent.getAction());
+ if (debug) Slog.v(TAG, "Typed Action list: " + Arrays.toString(firstTypeCut));
+ }
+ }
+ }
+
+ // If the intent includes a data URI, then we want to collect all of
+ // the filters that match its scheme (we will further refine matches
+ // on the authority and path by directly matching each resulting filter).
+ if (scheme != null) {
+ schemeCut = mSchemeToFilter.get(scheme);
+ if (debug) Slog.v(TAG, "Scheme list: " + Arrays.toString(schemeCut));
+ }
+
+ // If the intent does not specify any data -- either a MIME type or
+ // a URI -- then we will only be looking for matches against empty
+ // data.
+ if (resolvedType == null && scheme == null && intent.getAction() != null) {
+ firstTypeCut = mActionToFilter.get(intent.getAction());
+ if (debug) Slog.v(TAG, "Action list: " + Arrays.toString(firstTypeCut));
+ }
+
+ FastImmutableArraySet<String> categories = getFastIntentCategories(intent);
+ if (firstTypeCut != null) {
+ buildResolveList(intent, categories, debug, defaultOnly,
+ resolvedType, scheme, firstTypeCut, finalList, userId);
+ }
+ if (secondTypeCut != null) {
+ buildResolveList(intent, categories, debug, defaultOnly,
+ resolvedType, scheme, secondTypeCut, finalList, userId);
+ }
+ if (thirdTypeCut != null) {
+ buildResolveList(intent, categories, debug, defaultOnly,
+ resolvedType, scheme, thirdTypeCut, finalList, userId);
+ }
+ if (schemeCut != null) {
+ buildResolveList(intent, categories, debug, defaultOnly,
+ resolvedType, scheme, schemeCut, finalList, userId);
+ }
+ sortResults(finalList);
+
+ if (debug) {
+ Slog.v(TAG, "Final result list:");
+ for (int i=0; i<finalList.size(); i++) {
+ Slog.v(TAG, " " + finalList.get(i));
+ }
+ }
+ return finalList;
+ }
+
+ /**
+ * Control whether the given filter is allowed to go into the result
+ * list. Mainly intended to prevent adding multiple filters for the
+ * same target object.
+ */
+ protected boolean allowFilterResult(F filter, List<R> dest) {
+ return true;
+ }
+
+ /**
+ * Returns whether the object associated with the given filter is
+ * "stopped," that is whether it should not be included in the result
+ * if the intent requests to excluded stopped objects.
+ */
+ protected boolean isFilterStopped(F filter, int userId) {
+ return false;
+ }
+
+ /**
+ * Returns whether this filter is owned by this package. This must be
+ * implemented to provide correct filtering of Intents that have
+ * specified a package name they are to be delivered to.
+ */
+ protected abstract boolean isPackageForFilter(String packageName, F filter);
+
+ protected abstract F[] newArray(int size);
+
+ @SuppressWarnings("unchecked")
+ protected R newResult(F filter, int match, int userId) {
+ return (R)filter;
+ }
+
+ @SuppressWarnings("unchecked")
+ protected void sortResults(List<R> results) {
+ Collections.sort(results, mResolvePrioritySorter);
+ }
+
+ protected void dumpFilter(PrintWriter out, String prefix, F filter) {
+ out.print(prefix); out.println(filter);
+ }
+
+ private final void addFilter(ArrayMap<String, F[]> map, String name, F filter) {
+ F[] array = map.get(name);
+ if (array == null) {
+ array = newArray(2);
+ map.put(name, array);
+ array[0] = filter;
+ } else {
+ final int N = array.length;
+ int i = N;
+ while (i > 0 && array[i-1] == null) {
+ i--;
+ }
+ if (i < N) {
+ array[i] = filter;
+ } else {
+ F[] newa = newArray((N*3)/2);
+ System.arraycopy(array, 0, newa, 0, N);
+ newa[N] = filter;
+ map.put(name, newa);
+ }
+ }
+ }
+
+ private final int register_mime_types(F filter, String prefix) {
+ final Iterator<String> i = filter.typesIterator();
+ if (i == null) {
+ return 0;
+ }
+
+ int num = 0;
+ while (i.hasNext()) {
+ String name = i.next();
+ num++;
+ if (localLOGV) Slog.v(TAG, prefix + name);
+ String baseName = name;
+ final int slashpos = name.indexOf('/');
+ if (slashpos > 0) {
+ baseName = name.substring(0, slashpos).intern();
+ } else {
+ name = name + "/*";
+ }
+
+ addFilter(mTypeToFilter, name, filter);
+
+ if (slashpos > 0) {
+ addFilter(mBaseTypeToFilter, baseName, filter);
+ } else {
+ addFilter(mWildTypeToFilter, baseName, filter);
+ }
+ }
+
+ return num;
+ }
+
+ private final int unregister_mime_types(F filter, String prefix) {
+ final Iterator<String> i = filter.typesIterator();
+ if (i == null) {
+ return 0;
+ }
+
+ int num = 0;
+ while (i.hasNext()) {
+ String name = i.next();
+ num++;
+ if (localLOGV) Slog.v(TAG, prefix + name);
+ String baseName = name;
+ final int slashpos = name.indexOf('/');
+ if (slashpos > 0) {
+ baseName = name.substring(0, slashpos).intern();
+ } else {
+ name = name + "/*";
+ }
+
+ remove_all_objects(mTypeToFilter, name, filter);
+
+ if (slashpos > 0) {
+ remove_all_objects(mBaseTypeToFilter, baseName, filter);
+ } else {
+ remove_all_objects(mWildTypeToFilter, baseName, filter);
+ }
+ }
+ return num;
+ }
+
+ private final int register_intent_filter(F filter, Iterator<String> i,
+ ArrayMap<String, F[]> dest, String prefix) {
+ if (i == null) {
+ return 0;
+ }
+
+ int num = 0;
+ while (i.hasNext()) {
+ String name = i.next();
+ num++;
+ if (localLOGV) Slog.v(TAG, prefix + name);
+ addFilter(dest, name, filter);
+ }
+ return num;
+ }
+
+ private final int unregister_intent_filter(F filter, Iterator<String> i,
+ ArrayMap<String, F[]> dest, String prefix) {
+ if (i == null) {
+ return 0;
+ }
+
+ int num = 0;
+ while (i.hasNext()) {
+ String name = i.next();
+ num++;
+ if (localLOGV) Slog.v(TAG, prefix + name);
+ remove_all_objects(dest, name, filter);
+ }
+ return num;
+ }
+
+ private final void remove_all_objects(ArrayMap<String, F[]> map, String name,
+ Object object) {
+ F[] array = map.get(name);
+ if (array != null) {
+ int LAST = array.length-1;
+ while (LAST >= 0 && array[LAST] == null) {
+ LAST--;
+ }
+ for (int idx=LAST; idx>=0; idx--) {
+ if (array[idx] == object) {
+ final int remain = LAST - idx;
+ if (remain > 0) {
+ System.arraycopy(array, idx+1, array, idx, remain);
+ }
+ array[LAST] = null;
+ LAST--;
+ }
+ }
+ if (LAST < 0) {
+ map.remove(name);
+ } else if (LAST < (array.length/2)) {
+ F[] newa = newArray(LAST+2);
+ System.arraycopy(array, 0, newa, 0, LAST+1);
+ map.put(name, newa);
+ }
+ }
+ }
+
+ private static FastImmutableArraySet<String> getFastIntentCategories(Intent intent) {
+ final Set<String> categories = intent.getCategories();
+ if (categories == null) {
+ return null;
+ }
+ return new FastImmutableArraySet<String>(categories.toArray(new String[categories.size()]));
+ }
+
+ private void buildResolveList(Intent intent, FastImmutableArraySet<String> categories,
+ boolean debug, boolean defaultOnly,
+ String resolvedType, String scheme, F[] src, List<R> dest, int userId) {
+ final String action = intent.getAction();
+ final Uri data = intent.getData();
+ final String packageName = intent.getPackage();
+
+ final boolean excludingStopped = intent.isExcludingStopped();
+
+ final Printer logPrinter;
+ final PrintWriter logPrintWriter;
+ if (debug) {
+ logPrinter = new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM);
+ logPrintWriter = new FastPrintWriter(logPrinter);
+ } else {
+ logPrinter = null;
+ logPrintWriter = null;
+ }
+
+ final int N = src != null ? src.length : 0;
+ boolean hasNonDefaults = false;
+ int i;
+ F filter;
+ for (i=0; i<N && (filter=src[i]) != null; i++) {
+ int match;
+ if (debug) Slog.v(TAG, "Matching against filter " + filter);
+
+ if (excludingStopped && isFilterStopped(filter, userId)) {
+ if (debug) {
+ Slog.v(TAG, " Filter's target is stopped; skipping");
+ }
+ continue;
+ }
+
+ // Is delivery being limited to filters owned by a particular package?
+ if (packageName != null && !isPackageForFilter(packageName, filter)) {
+ if (debug) {
+ Slog.v(TAG, " Filter is not from package " + packageName + "; skipping");
+ }
+ continue;
+ }
+
+ // Do we already have this one?
+ if (!allowFilterResult(filter, dest)) {
+ if (debug) {
+ Slog.v(TAG, " Filter's target already added");
+ }
+ continue;
+ }
+
+ match = filter.match(action, resolvedType, scheme, data, categories, TAG);
+ if (match >= 0) {
+ if (debug) Slog.v(TAG, " Filter matched! match=0x" +
+ Integer.toHexString(match) + " hasDefault="
+ + filter.hasCategory(Intent.CATEGORY_DEFAULT));
+ if (!defaultOnly || filter.hasCategory(Intent.CATEGORY_DEFAULT)) {
+ final R oneResult = newResult(filter, match, userId);
+ if (oneResult != null) {
+ dest.add(oneResult);
+ if (debug) {
+ dumpFilter(logPrintWriter, " ", filter);
+ logPrintWriter.flush();
+ filter.dump(logPrinter, " ");
+ }
+ }
+ } else {
+ hasNonDefaults = true;
+ }
+ } else {
+ if (debug) {
+ String reason;
+ switch (match) {
+ case IntentFilter.NO_MATCH_ACTION: reason = "action"; break;
+ case IntentFilter.NO_MATCH_CATEGORY: reason = "category"; break;
+ case IntentFilter.NO_MATCH_DATA: reason = "data"; break;
+ case IntentFilter.NO_MATCH_TYPE: reason = "type"; break;
+ default: reason = "unknown reason"; break;
+ }
+ Slog.v(TAG, " Filter did not match: " + reason);
+ }
+ }
+ }
+
+ if (hasNonDefaults) {
+ if (dest.size() == 0) {
+ Slog.w(TAG, "resolveIntent failed: found match, but none with CATEGORY_DEFAULT");
+ } else if (dest.size() > 1) {
+ Slog.w(TAG, "resolveIntent: multiple matches, only some with CATEGORY_DEFAULT");
+ }
+ }
+ }
+
+ // Sorts a List of IntentFilter objects into descending priority order.
+ @SuppressWarnings("rawtypes")
+ private static final Comparator mResolvePrioritySorter = new Comparator() {
+ public int compare(Object o1, Object o2) {
+ final int q1 = ((IntentFilter) o1).getPriority();
+ final int q2 = ((IntentFilter) o2).getPriority();
+ return (q1 > q2) ? -1 : ((q1 < q2) ? 1 : 0);
+ }
+ };
+
+ /**
+ * All filters that have been registered.
+ */
+ private final HashSet<F> mFilters = new HashSet<F>();
+
+ /**
+ * All of the MIME types that have been registered, such as "image/jpeg",
+ * "image/*", or "{@literal *}/*".
+ */
+ private final ArrayMap<String, F[]> mTypeToFilter = new ArrayMap<String, F[]>();
+
+ /**
+ * The base names of all of all fully qualified MIME types that have been
+ * registered, such as "image" or "*". Wild card MIME types such as
+ * "image/*" will not be here.
+ */
+ private final ArrayMap<String, F[]> mBaseTypeToFilter = new ArrayMap<String, F[]>();
+
+ /**
+ * The base names of all of the MIME types with a sub-type wildcard that
+ * have been registered. For example, a filter with "image/*" will be
+ * included here as "image" but one with "image/jpeg" will not be
+ * included here. This also includes the "*" for the "{@literal *}/*"
+ * MIME type.
+ */
+ private final ArrayMap<String, F[]> mWildTypeToFilter = new ArrayMap<String, F[]>();
+
+ /**
+ * All of the URI schemes (such as http) that have been registered.
+ */
+ private final ArrayMap<String, F[]> mSchemeToFilter = new ArrayMap<String, F[]>();
+
+ /**
+ * All of the actions that have been registered, but only those that did
+ * not specify data.
+ */
+ private final ArrayMap<String, F[]> mActionToFilter = new ArrayMap<String, F[]>();
+
+ /**
+ * All of the actions that have been registered and specified a MIME type.
+ */
+ private final ArrayMap<String, F[]> mTypedActionToFilter = new ArrayMap<String, F[]>();
+}