package com.lody.virtual.server.pm; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.Build; import com.lody.virtual.helper.utils.VLog; import com.lody.virtual.server.pm.parser.VPackage; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; public abstract class IntentResolver<F extends VPackage.IntentInfo, R extends Object> { private static final String TAG = "IntentResolver"; // Sorts a List of IntentFilter objects into descending priority order. @SuppressWarnings("rawtypes") private static final Comparator sResolvePrioritySorter = new Comparator() { public int compare(Object o1, Object o2) { int q1; int q2; if (o1 instanceof IntentFilter) { q1 = ((IntentFilter) o1).getPriority(); q2 = ((IntentFilter) o2).getPriority(); } else if (o1 instanceof ResolveInfo) { ResolveInfo r1 = (ResolveInfo) o1; ResolveInfo r2 = (ResolveInfo) o2; q1 = r1.filter == null ? 0 : r1.filter.getPriority(); q2 = r2.filter == null ? 0 : r2.filter.getPriority(); } else { return 0; } return (q1 > q2) ? -1 : ((q1 < q2) ? 1 : 0); } }; /** * All filters that have been registered. */ private HashSet<F> mFilters = new HashSet<F>(); /** * All of the MIME types that have been registered, such as "image/jpeg", * "image/*", or "{@literal *}/*". */ private HashMap<String, F[]> mTypeToFilter = new HashMap<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 HashMap<String, F[]> mBaseTypeToFilter = new HashMap<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 HashMap<String, F[]> mWildTypeToFilter = new HashMap<String, F[]>(); /** * All of the URI schemes (such as http) that have been registered. */ private HashMap<String, F[]> mSchemeToFilter = new HashMap<String, F[]>(); /** * All of the actions that have been registered, but only those that did not * specify data. */ private HashMap<String, F[]> mActionToFilter = new HashMap<String, F[]>(); /** * All of the actions that have been registered and specified a MIME type. */ private HashMap<String, F[]> mTypedActionToFilter = new HashMap<String, F[]>(); 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()])); } public void addFilter(F f) { mFilters.add(f); int numS = register_intent_filter(f, f.filter.schemesIterator(), mSchemeToFilter, " Scheme: "); int numT = register_mime_types(f, " Type: "); if (numS == 0 && numT == 0) { register_intent_filter(f, f.filter.actionsIterator(), mActionToFilter, " Action: "); } if (numT != 0) { register_intent_filter(f, f.filter.actionsIterator(), mTypedActionToFilter, " TypedAction: "); } } private boolean filterEquals(IntentFilter f1, IntentFilter f2) { int s1 = f1.countActions(); int s2 = f2.countActions(); if (s1 != s2) { return false; } for (int i = 0; i < s1; i++) { if (!f2.hasAction(f1.getAction(i))) { return false; } } s1 = f1.countCategories(); s2 = f2.countCategories(); if (s1 != s2) { return false; } for (int i = 0; i < s1; i++) { if (!f2.hasCategory(f1.getCategory(i))) { return false; } } s1 = f1.countDataTypes(); s2 = f2.countDataTypes(); if (s1 != s2) { return false; } s1 = f1.countDataSchemes(); s2 = f2.countDataSchemes(); if (s1 != s2) { return false; } for (int i = 0; i < s1; i++) { if (!f2.hasDataScheme(f1.getDataScheme(i))) { return false; } } s1 = f1.countDataAuthorities(); s2 = f2.countDataAuthorities(); if (s1 != s2) { return false; } s1 = f1.countDataPaths(); s2 = f2.countDataPaths(); if (s1 != s2) { return false; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { s1 = f1.countDataSchemeSpecificParts(); s2 = f2.countDataSchemeSpecificParts(); if (s1 != s2) { return false; } } return true; } private ArrayList<F> collectFilters(F[] array, IntentFilter matching) { ArrayList<F> res = null; if (array != null) { for (int i = 0; i < array.length; i++) { F cur = array[i]; if (cur == null) { break; } if (filterEquals(cur.filter, matching)) { if (res == null) { res = new ArrayList<>(); } res.add(cur); } } } return res; } public ArrayList<F> findFilters(IntentFilter matching) { if (matching.countDataSchemes() == 1) { // Fast case. return collectFilters(mSchemeToFilter.get(matching.getDataScheme(0)), matching); } else if (matching.countDataTypes() != 0 && matching.countActions() == 1) { // Another fast case. return collectFilters(mTypedActionToFilter.get(matching.getAction(0)), matching); } else if (matching.countDataTypes() == 0 && matching.countDataSchemes() == 0 && matching.countActions() == 1) { // Last fast case. return collectFilters(mActionToFilter.get(matching.getAction(0)), matching); } else { ArrayList<F> res = null; for (F cur : mFilters) { if (filterEquals(cur.filter, matching)) { if (res == null) { res = new ArrayList<>(); } res.add(cur); } } return res; } } public void removeFilter(F f) { removeFilterInternal(f); mFilters.remove(f); } void removeFilterInternal(F f) { int numS = unregister_intent_filter(f, f.filter.schemesIterator(), mSchemeToFilter, " Scheme: "); int numT = unregister_mime_types(f, " Type: "); if (numS == 0 && numT == 0) { unregister_intent_filter(f, f.filter.actionsIterator(), mActionToFilter, " Action: "); } if (numT != 0) { unregister_intent_filter(f, f.filter.actionsIterator(), mTypedActionToFilter, " TypedAction: "); } } /** * 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>(); FastImmutableArraySet<String> categories = getFastIntentCategories(intent); final String scheme = intent.getScheme(); int N = listCut.size(); for (int i = 0; i < N; ++i) { buildResolveList(intent, categories, 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>(); 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); secondTypeCut = mWildTypeToFilter.get(baseType); } else { // We can match anything with our base type. firstTypeCut = mBaseTypeToFilter.get(baseType); secondTypeCut = mWildTypeToFilter.get(baseType); } // Any */* types always apply, but we only need to do this // if the intent type was not already */*. thirdTypeCut = mWildTypeToFilter.get("*"); } 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 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 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()); } FastImmutableArraySet<String> categories = getFastIntentCategories(intent); if (firstTypeCut != null) { buildResolveList(intent, categories, defaultOnly, resolvedType, scheme, firstTypeCut, finalList, userId); } if (secondTypeCut != null) { buildResolveList(intent, categories, defaultOnly, resolvedType, scheme, secondTypeCut, finalList, userId); } if (thirdTypeCut != null) { buildResolveList(intent, categories, defaultOnly, resolvedType, scheme, thirdTypeCut, finalList, userId); } if (schemeCut != null) { buildResolveList(intent, categories, defaultOnly, resolvedType, scheme, schemeCut, finalList, userId); } sortResults(finalList); 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) { 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, sResolvePrioritySorter); } protected void dumpFilter(PrintWriter out, String prefix, F filter) { out.print(prefix); out.println(filter); } protected Object filterToLabel(F filter) { return "IntentFilter"; } protected void dumpFilterLabel(PrintWriter out, String prefix, Object label, int count) { out.print(prefix); out.print(label); out.print(": "); out.println(count); } private void addFilter(HashMap<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 int register_mime_types(F filter, String prefix) { final Iterator<String> i = filter.filter.typesIterator(); if (i == null) { return 0; } int num = 0; while (i.hasNext()) { String name = i.next(); num++; 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 int unregister_mime_types(F filter, String prefix) { final Iterator<String> i = filter.filter.typesIterator(); if (i == null) { return 0; } int num = 0; while (i.hasNext()) { String name = i.next(); num++; 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 int register_intent_filter(F filter, Iterator<String> i, HashMap<String, F[]> dest, String prefix) { if (i == null) { return 0; } int num = 0; while (i.hasNext()) { String name = i.next(); num++; addFilter(dest, name, filter); } return num; } private int unregister_intent_filter(F filter, Iterator<String> i, HashMap<String, F[]> dest, String prefix) { if (i == null) { return 0; } int num = 0; while (i.hasNext()) { String name = i.next(); num++; remove_all_objects(dest, name, filter); } return num; } private void remove_all_objects(HashMap<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 void buildResolveList(Intent intent, FastImmutableArraySet<String> categories, 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 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; // Is delivery being limited to filters owned by a particular // package? if (packageName != null && !isPackageForFilter(packageName, filter)) { continue; } // Do we already have this one? if (!allowFilterResult(filter, dest)) { continue; } match = filter.filter.match(action, resolvedType, scheme, data, categories, TAG); if (match >= 0) { if (!defaultOnly || filter.filter.hasCategory(Intent.CATEGORY_DEFAULT)) { final R oneResult = newResult(filter, match, userId); if (oneResult != null) { dest.add(oneResult); } } else { hasNonDefaults = true; } } } if (hasNonDefaults) { if (dest.size() == 0) { VLog.w(TAG, "resolveIntent failed: found match, but none with CATEGORY_DEFAULT"); } else if (dest.size() > 1) { VLog.w(TAG, "resolveIntent: multiple matches, only some with CATEGORY_DEFAULT"); } } } private class IteratorWrapper implements Iterator<F> { private 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(); } } }