diff options
| author | 2024-01-26 18:41:35 +0000 | |
|---|---|---|
| committer | 2024-01-26 18:41:35 +0000 | |
| commit | e8ae59a7f2862ad2fa392e19bb5d0821e62546e1 (patch) | |
| tree | 26511785279b825e082116287be084e32447ef70 | |
| parent | 9d77c522244106140585f9c0ad790e69393615ad (diff) | |
| parent | d31458436119bc9e78477855cfca20f92c818fbc (diff) | |
Merge "Add <uri-relative-filter-group> to intent filters." into main
| -rw-r--r-- | core/api/current.txt | 35 | ||||
| -rw-r--r-- | core/java/android/content/IntentFilter.java | 140 | ||||
| -rw-r--r-- | core/java/android/content/UriRelativeFilter.java | 260 | ||||
| -rw-r--r-- | core/java/android/content/UriRelativeFilterGroup.java | 216 | ||||
| -rw-r--r-- | core/java/android/content/pm/flags.aconfig | 8 | ||||
| -rw-r--r-- | core/java/android/os/PatternMatcher.java | 14 | ||||
| -rw-r--r-- | core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java | 200 | ||||
| -rw-r--r-- | core/proto/android/content/intent.proto | 23 | ||||
| -rw-r--r-- | core/res/res/values/attrs_manifest.xml | 81 | ||||
| -rw-r--r-- | core/res/res/values/public-staging.xml | 20 | ||||
| -rw-r--r-- | nfc/jarjar-rules.txt | 1 | ||||
| -rw-r--r-- | services/tests/PackageManagerServiceTests/server/Android.bp | 1 | ||||
| -rw-r--r-- | services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java | 104 | ||||
| -rw-r--r-- | services/tests/servicestests/test-apps/PackageParserApp/Android.bp | 14 | ||||
| -rw-r--r-- | services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp7.xml | 67 | ||||
| -rw-r--r-- | tools/aapt2/link/ManifestFixer.cpp | 2 |
16 files changed, 1179 insertions, 7 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 8de5335a9b72..66feebc6cc92 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -444,6 +444,7 @@ package android { field public static final int alertDialogTheme = 16843529; // 0x1010309 field public static final int alignmentMode = 16843642; // 0x101037a field public static final int allContactsName = 16843468; // 0x10102cc + field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int allow; field public static final int allowAudioPlaybackCapture = 16844289; // 0x1010601 field public static final int allowBackup = 16843392; // 0x1010280 field public static final int allowClearUserData = 16842757; // 0x1010005 @@ -847,6 +848,7 @@ package android { field public static final int format24Hour = 16843723; // 0x10103cb field public static final int fraction = 16843992; // 0x10104d8 field public static final int fragment = 16843491; // 0x10102e3 + field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int fragmentAdvancedPattern; field public static final int fragmentAllowEnterTransitionOverlap = 16843976; // 0x10104c8 field public static final int fragmentAllowReturnTransitionOverlap = 16843977; // 0x10104c9 field public static final int fragmentCloseEnterAnimation = 16843495; // 0x10102e7 @@ -857,10 +859,13 @@ package android { field public static final int fragmentFadeExitAnimation = 16843498; // 0x10102ea field public static final int fragmentOpenEnterAnimation = 16843493; // 0x10102e5 field public static final int fragmentOpenExitAnimation = 16843494; // 0x10102e6 + field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int fragmentPattern; + field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int fragmentPrefix; field public static final int fragmentReenterTransition = 16843975; // 0x10104c7 field public static final int fragmentReturnTransition = 16843973; // 0x10104c5 field public static final int fragmentSharedElementEnterTransition = 16843972; // 0x10104c4 field public static final int fragmentSharedElementReturnTransition = 16843974; // 0x10104c6 + field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int fragmentSuffix; field public static final int freezesText = 16843116; // 0x101016c field public static final int fromAlpha = 16843210; // 0x10101ca field public static final int fromDegrees = 16843187; // 0x10101b3 @@ -1329,10 +1334,15 @@ package android { field public static final int propertyYName = 16843893; // 0x1010475 field public static final int protectionLevel = 16842761; // 0x1010009 field public static final int publicKey = 16843686; // 0x10103a6 + field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int query; field public static final int queryActionMsg = 16843227; // 0x10101db + field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int queryAdvancedPattern; field public static final int queryAfterZeroResults = 16843394; // 0x1010282 field public static final int queryBackground = 16843911; // 0x1010487 field public static final int queryHint = 16843608; // 0x1010358 + field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int queryPattern; + field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int queryPrefix; + field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int querySuffix; field public static final int quickContactBadgeStyleSmallWindowLarge = 16843443; // 0x10102b3 field public static final int quickContactBadgeStyleSmallWindowMedium = 16843442; // 0x10102b2 field public static final int quickContactBadgeStyleSmallWindowSmall = 16843441; // 0x10102b1 @@ -11387,10 +11397,12 @@ package android.content { method public final void addDataScheme(String); method public final void addDataSchemeSpecificPart(String, int); method public final void addDataType(String) throws android.content.IntentFilter.MalformedMimeTypeException; + method @FlaggedApi("android.content.pm.relative_reference_intent_filters") public final void addUriRelativeFilterGroup(@NonNull android.content.UriRelativeFilterGroup); method @NonNull public java.util.function.Predicate<android.content.Intent> asPredicate(); method @NonNull public java.util.function.Predicate<android.content.Intent> asPredicateWithTypeResolution(@NonNull android.content.ContentResolver); method public final java.util.Iterator<android.content.IntentFilter.AuthorityEntry> authoritiesIterator(); method public final java.util.Iterator<java.lang.String> categoriesIterator(); + method @FlaggedApi("android.content.pm.relative_reference_intent_filters") public final void clearUriRelativeFilterGroups(); method public final int countActions(); method public final int countCategories(); method public final int countDataAuthorities(); @@ -11398,6 +11410,7 @@ package android.content { method public final int countDataSchemeSpecificParts(); method public final int countDataSchemes(); method public final int countDataTypes(); + method @FlaggedApi("android.content.pm.relative_reference_intent_filters") public final int countUriRelativeFilterGroups(); method public static android.content.IntentFilter create(String, String); method public final int describeContents(); method public void dump(android.util.Printer, String); @@ -11409,6 +11422,7 @@ package android.content { method public final android.os.PatternMatcher getDataSchemeSpecificPart(int); method public final String getDataType(int); method public final int getPriority(); + method @FlaggedApi("android.content.pm.relative_reference_intent_filters") @NonNull public final android.content.UriRelativeFilterGroup getUriRelativeFilterGroup(int); method public final boolean hasAction(String); method public final boolean hasCategory(String); method public final boolean hasDataAuthority(android.net.Uri); @@ -11830,6 +11844,27 @@ package android.content { field public static final long INVALID_TIME = -9223372036854775808L; // 0x8000000000000000L } + @FlaggedApi("android.content.pm.relative_reference_intent_filters") public final class UriRelativeFilter { + ctor public UriRelativeFilter(int, int, @NonNull String); + method @NonNull public String getFilter(); + method public int getPatternType(); + method public int getUriPart(); + method public boolean matchData(@NonNull android.net.Uri); + field public static final int FRAGMENT = 2; // 0x2 + field public static final int PATH = 0; // 0x0 + field public static final int QUERY = 1; // 0x1 + } + + @FlaggedApi("android.content.pm.relative_reference_intent_filters") public final class UriRelativeFilterGroup { + ctor public UriRelativeFilterGroup(int); + method public void addUriRelativeFilter(@NonNull android.content.UriRelativeFilter); + method public int getAction(); + method @NonNull public java.util.Collection<android.content.UriRelativeFilter> getUriRelativeFilters(); + method public boolean matchData(@NonNull android.net.Uri); + field public static final int ACTION_ALLOW = 0; // 0x0 + field public static final int ACTION_BLOCK = 1; // 0x1 + } + } package android.content.om { diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java index ad3acd713c6b..79af65a3a3e5 100644 --- a/core/java/android/content/IntentFilter.java +++ b/core/java/android/content/IntentFilter.java @@ -16,11 +16,13 @@ package android.content; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; +import android.content.pm.Flags; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -175,6 +177,7 @@ public class IntentFilter implements Parcelable { private static final String ACTION_STR = "action"; private static final String AUTO_VERIFY_STR = "autoVerify"; private static final String EXTRAS_STR = "extras"; + private static final String URI_RELATIVE_FILTER_GROUP_STR = "uriRelativeFilterGroup"; private static final int[] EMPTY_INT_ARRAY = new int[0]; private static final long[] EMPTY_LONG_ARRAY = new long[0]; @@ -324,6 +327,7 @@ public class IntentFilter implements Parcelable { private ArrayList<PatternMatcher> mDataSchemeSpecificParts = null; private ArrayList<AuthorityEntry> mDataAuthorities = null; private ArrayList<PatternMatcher> mDataPaths = null; + private ArrayList<UriRelativeFilterGroup> mUriRelativeFilterGroups = null; private ArrayList<String> mStaticDataTypes = null; private ArrayList<String> mDataTypes = null; private ArrayList<String> mMimeGroups = null; @@ -520,6 +524,10 @@ public class IntentFilter implements Parcelable { if (o.mDataPaths != null) { mDataPaths = new ArrayList<PatternMatcher>(o.mDataPaths); } + if (o.mUriRelativeFilterGroups != null) { + mUriRelativeFilterGroups = + new ArrayList<UriRelativeFilterGroup>(o.mUriRelativeFilterGroups); + } if (o.mMimeGroups != null) { mMimeGroups = new ArrayList<String>(o.mMimeGroups); } @@ -1563,6 +1571,63 @@ public class IntentFilter implements Parcelable { } /** + * Add a new URI relative filter group to match against the Intent data. The + * intent filter must include one or more schemes (via {@link #addDataScheme}) + * <em>and</em> one or more authorities (via {@link #addDataAuthority}) for + * the group to be considered. + * + * <p>Groups will be matched in the order they were added and matching will only + * be done if no data paths match or if none are included. If both data paths and + * groups are not included, then only the scheme/authority must match.</p> + * + * @param group A {@link UriRelativeFilterGroup} to match the URI. + * + * @see UriRelativeFilterGroup + * @see #matchData + * @see #addDataScheme + * @see #addDataAuthority + */ + @FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS) + public final void addUriRelativeFilterGroup(@NonNull UriRelativeFilterGroup group) { + Objects.requireNonNull(group); + if (mUriRelativeFilterGroups == null) { + mUriRelativeFilterGroups = new ArrayList<>(); + } + mUriRelativeFilterGroups.add(group); + } + + /** + * Return the number of URI relative filter groups in the intent filter. + */ + @FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS) + public final int countUriRelativeFilterGroups() { + return mUriRelativeFilterGroups == null ? 0 : mUriRelativeFilterGroups.size(); + } + + /** + * Return a URI relative filter group in the intent filter. + * + * <p>Note: use of this method will result in a NullPointerException + * if no groups exists for this intent filter.</p> + * + * @param index index of the element to return + * @throws IndexOutOfBoundsException if index is out of range + */ + @FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS) + @NonNull + public final UriRelativeFilterGroup getUriRelativeFilterGroup(int index) { + return mUriRelativeFilterGroups.get(index); + } + + /** + * Removes all existing URI relative filter groups in the intent filter. + */ + @FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS) + public final void clearUriRelativeFilterGroups() { + mUriRelativeFilterGroups = null; + } + + /** * Match this intent filter against the given Intent data. This ignores * the data scheme -- unlike {@link #matchData}, the authority will match * regardless of whether there is a matching scheme. @@ -1677,12 +1742,24 @@ public class IntentFilter implements Parcelable { int authMatch = matchDataAuthority(data, wildcardSupported); if (authMatch >= 0) { final ArrayList<PatternMatcher> paths = mDataPaths; - if (paths == null) { - match = authMatch; - } else if (hasDataPath(data.getPath(), wildcardSupported)) { - match = MATCH_CATEGORY_PATH; + final ArrayList<UriRelativeFilterGroup> groups = mUriRelativeFilterGroups; + if (Flags.relativeReferenceIntentFilters()) { + if (paths == null && groups == null) { + match = authMatch; + } else if (hasDataPath(data.getPath(), wildcardSupported) + || matchRelRefGroups(data)) { + match = MATCH_CATEGORY_PATH; + } else { + return NO_MATCH_DATA; + } } else { - return NO_MATCH_DATA; + if (paths == null) { + match = authMatch; + } else if (hasDataPath(data.getPath(), wildcardSupported)) { + match = MATCH_CATEGORY_PATH; + } else { + return NO_MATCH_DATA; + } } } else { return NO_MATCH_DATA; @@ -1726,6 +1803,19 @@ public class IntentFilter implements Parcelable { return match + MATCH_ADJUSTMENT_NORMAL; } + private boolean matchRelRefGroups(Uri data) { + if (mUriRelativeFilterGroups == null) { + return false; + } + for (int i = 0; i < mUriRelativeFilterGroups.size(); i++) { + UriRelativeFilterGroup group = mUriRelativeFilterGroups.get(i); + if (group.matchData(data)) { + return group.getAction() == UriRelativeFilterGroup.ACTION_ALLOW; + } + } + return false; + } + /** * Add a new Intent category to match against. The semantics of * categories is the opposite of actions -- an Intent includes the @@ -2486,6 +2576,12 @@ public class IntentFilter implements Parcelable { } serializer.endTag(null, EXTRAS_STR); } + if (Flags.relativeReferenceIntentFilters()) { + N = countUriRelativeFilterGroups(); + for (int i = 0; i < N; i++) { + mUriRelativeFilterGroups.get(i).writeToXml(serializer); + } + } } /** @@ -2614,6 +2710,9 @@ public class IntentFilter implements Parcelable { } } else if (tagName.equals(EXTRAS_STR)) { mExtras = PersistableBundle.restoreFromXml(parser); + } else if (Flags.relativeReferenceIntentFilters() + && URI_RELATIVE_FILTER_GROUP_STR.equals(tagName)) { + addUriRelativeFilterGroup(new UriRelativeFilterGroup(parser)); } else { Log.w("IntentFilter", "Unknown tag parsing IntentFilter: " + tagName); } @@ -2680,6 +2779,12 @@ public class IntentFilter implements Parcelable { if (mExtras != null) { mExtras.dumpDebug(proto, IntentFilterProto.EXTRAS); } + if (Flags.relativeReferenceIntentFilters() && mUriRelativeFilterGroups != null) { + Iterator<UriRelativeFilterGroup> it = mUriRelativeFilterGroups.iterator(); + while (it.hasNext()) { + it.next().dumpDebug(proto, IntentFilterProto.URI_RELATIVE_FILTER_GROUPS); + } + } proto.end(token); } @@ -2744,6 +2849,15 @@ public class IntentFilter implements Parcelable { du.println(sb.toString()); } } + if (mUriRelativeFilterGroups != null) { + Iterator<UriRelativeFilterGroup> it = mUriRelativeFilterGroups.iterator(); + while (it.hasNext()) { + sb.setLength(0); + sb.append(prefix); sb.append("UriRelativeFilterGroup: \""); + sb.append(it.next()); sb.append("\""); + du.println(sb.toString()); + } + } if (mStaticDataTypes != null) { Iterator<String> it = mStaticDataTypes.iterator(); while (it.hasNext()) { @@ -2883,6 +2997,15 @@ public class IntentFilter implements Parcelable { } else { dest.writeInt(0); } + if (Flags.relativeReferenceIntentFilters() && mUriRelativeFilterGroups != null) { + final int N = mUriRelativeFilterGroups.size(); + dest.writeInt(N); + for (int i = 0; i < N; i++) { + mUriRelativeFilterGroups.get(i).writeToParcel(dest, flags); + } + } else { + dest.writeInt(0); + } } /** @@ -2989,6 +3112,13 @@ public class IntentFilter implements Parcelable { if (source.readInt() != 0) { mExtras = PersistableBundle.CREATOR.createFromParcel(source); } + N = source.readInt(); + if (Flags.relativeReferenceIntentFilters() && N > 0) { + mUriRelativeFilterGroups = new ArrayList<UriRelativeFilterGroup>(N); + for (int i = 0; i < N; i++) { + mUriRelativeFilterGroups.add(new UriRelativeFilterGroup(source)); + } + } } private boolean hasPartialTypes() { diff --git a/core/java/android/content/UriRelativeFilter.java b/core/java/android/content/UriRelativeFilter.java new file mode 100644 index 000000000000..9866cd0e992a --- /dev/null +++ b/core/java/android/content/UriRelativeFilter.java @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2024 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 android.content; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.pm.Flags; +import android.net.Uri; +import android.os.Parcel; +import android.os.PatternMatcher; +import android.util.proto.ProtoOutputStream; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * A filter for matching Intent URI Data as part of a + * {@link UriRelativeFilterGroup}. A single filter can only be + * matched against either a URI path, query or fragment + */ +@FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS) +public final class UriRelativeFilter { + private static final String FILTER_STR = "filter"; + private static final String PART_STR = "part"; + private static final String PATTERN_STR = "pattern"; + static final String URI_RELATIVE_FILTER_STR = "uriRelativeFilter"; + + /** + * Value to indicate that the filter is to be applied to a URI path. + */ + public static final int PATH = 0; + /** + * Value to indicate that the filter is to be applied to a URI query. + */ + public static final int QUERY = 1; + /** + * Value to indicate that the filter is to be applied to a URI fragment. + */ + public static final int FRAGMENT = 2; + + /** @hide */ + @IntDef(value = { + PATH, + QUERY, + FRAGMENT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface UriPart {} + + private final @UriPart int mUriPart; + private final @PatternMatcher.PatternType int mPatternType; + private final String mFilter; + + /** + * Creates a new UriRelativeFilter. + * + * @param uriPart The URI part this filter operates on. Can be either a + * {@link UriRelativeFilter#PATH}, {@link UriRelativeFilter#QUERY}, + * or {@link UriRelativeFilter#FRAGMENT}. + * @param patternType The pattern type of the filter. Can be either a + * {@link PatternMatcher#PATTERN_LITERAL}, + * {@link PatternMatcher#PATTERN_PREFIX}, +* {@link PatternMatcher#PATTERN_SUFFIX}, + * {@link PatternMatcher#PATTERN_SIMPLE_GLOB}, + * or {@link PatternMatcher#PATTERN_ADVANCED_GLOB}. + * @param filter A literal or pattern string depedning on patterType + * used to match a uriPart . + */ + public UriRelativeFilter( + @UriPart int uriPart, + @PatternMatcher.PatternType int patternType, + @NonNull String filter) { + mUriPart = uriPart; + com.android.internal.util.AnnotationValidations.validate( + UriPart.class, null, mUriPart); + mPatternType = patternType; + com.android.internal.util.AnnotationValidations.validate( + PatternMatcher.PatternType.class, null, mPatternType); + mFilter = filter; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mFilter); + } + + /** + * The URI part this filter operates on. + */ + public @UriPart int getUriPart() { + return mUriPart; + } + + /** + * The pattern type of the filter. + */ + public @PatternMatcher.PatternType int getPatternType() { + return mPatternType; + } + + /** + * The string used to filter the URI. + */ + public @NonNull String getFilter() { + return mFilter; + } + + /** + * Match this URI filter against an Intent's data. QUERY filters can + * match against any key value pair in the query string. PATH and + * FRAGMENT filters must match the entire string. + * + * @param data The full data string to match against, as supplied in + * Intent.data. + * + * @return true if there is a match. + */ + public boolean matchData(@NonNull Uri data) { + PatternMatcher pe = new PatternMatcher(mFilter, mPatternType); + switch (getUriPart()) { + case PATH: + return pe.match(data.getPath()); + case QUERY: + return matchQuery(pe, data.getQuery()); + case FRAGMENT: + return pe.match(data.getFragment()); + default: + return false; + } + } + + private boolean matchQuery(PatternMatcher pe, String query) { + if (query != null) { + String[] params = query.split("&"); + if (params.length == 1) { + params = query.split(";"); + } + for (int i = 0; i < params.length; i++) { + if (pe.match(params[i])) return true; + } + } + return false; + } + + /** @hide */ + public void dumpDebug(ProtoOutputStream proto, long fieldId) { + long token = proto.start(fieldId); + proto.write(UriRelativeFilterProto.URI_PART, mUriPart); + proto.write(UriRelativeFilterProto.PATTERN_TYPE, mPatternType); + proto.write(UriRelativeFilterProto.FILTER, mFilter); + proto.end(token); + } + + /** @hide */ + public void writeToXml(XmlSerializer serializer) throws IOException { + serializer.startTag(null, URI_RELATIVE_FILTER_STR); + serializer.attribute(null, PATTERN_STR, Integer.toString(mPatternType)); + serializer.attribute(null, PART_STR, Integer.toString(mUriPart)); + serializer.attribute(null, FILTER_STR, mFilter); + serializer.endTag(null, URI_RELATIVE_FILTER_STR); + } + + private String uriPartToString() { + switch (mUriPart) { + case PATH: + return "PATH"; + case QUERY: + return "QUERY"; + case FRAGMENT: + return "FRAGMENT"; + default: + return "UNKNOWN"; + } + } + + private String patternTypeToString() { + switch (mPatternType) { + case PatternMatcher.PATTERN_LITERAL: + return "LITERAL"; + case PatternMatcher.PATTERN_PREFIX: + return "PREFIX"; + case PatternMatcher.PATTERN_SIMPLE_GLOB: + return "GLOB"; + case PatternMatcher.PATTERN_ADVANCED_GLOB: + return "ADVANCED_GLOB"; + case PatternMatcher.PATTERN_SUFFIX: + return "SUFFIX"; + default: + return "UNKNOWN"; + } + } + + @Override + public String toString() { + return "UriRelativeFilter { " + + "uriPart = " + uriPartToString() + ", " + + "patternType = " + patternTypeToString() + ", " + + "filter = " + mFilter + + " }"; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + UriRelativeFilter that = (UriRelativeFilter) o; + return mUriPart == that.mUriPart + && mPatternType == that.mPatternType + && java.util.Objects.equals(mFilter, that.mFilter); + } + + @Override + public int hashCode() { + int _hash = 1; + _hash = 31 * _hash + mUriPart; + _hash = 31 * _hash + mPatternType; + _hash = 31 * _hash + java.util.Objects.hashCode(mFilter); + return _hash; + } + + /** @hide */ + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mUriPart); + dest.writeInt(mPatternType); + dest.writeString(mFilter); + } + + /** @hide */ + UriRelativeFilter(@NonNull android.os.Parcel in) { + mUriPart = in.readInt(); + mPatternType = in.readInt(); + mFilter = in.readString(); + } + + /** @hide */ + public UriRelativeFilter(XmlPullParser parser) throws XmlPullParserException, IOException { + mUriPart = Integer.parseInt(parser.getAttributeValue(null, PART_STR)); + mPatternType = Integer.parseInt(parser.getAttributeValue(null, PATTERN_STR)); + mFilter = parser.getAttributeValue(null, FILTER_STR); + } +} diff --git a/core/java/android/content/UriRelativeFilterGroup.java b/core/java/android/content/UriRelativeFilterGroup.java new file mode 100644 index 000000000000..72c396a73ec8 --- /dev/null +++ b/core/java/android/content/UriRelativeFilterGroup.java @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2024 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 android.content; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.content.pm.Flags; +import android.net.Uri; +import android.os.Parcel; +import android.util.ArraySet; +import android.util.Log; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.util.CollectionUtils; +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.Objects; + +/** + * An intent data matching group based on a URI's relative reference which + * includes the path, query and fragment. The group is only considered as + * matching if <em>all</em> UriRelativeFilters in the group match. Each + * UriRelativeFilter defines a matching rule for a URI path, query or fragment. + * A group must contain one or more UriRelativeFilters to match but does not need to + * contain UriRelativeFilters for all existing parts of a URI to match. + * + * <p>For example, given a URI that contains path, query and fragment parts, + * a group containing only a path filter will match the URI if the path + * filter matches the URI path. If the group contains a path and query + * filter, then the group will only match if both path and query filters + * match. If a URI contains only a path with no query or fragment then a + * group can only match if it contains only a matching path filter. If the + * group also contained additional query or fragment filters then it will + * not match.</p> + */ +@FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS) +public final class UriRelativeFilterGroup { + private static final String ALLOW_STR = "allow"; + private static final String URI_RELATIVE_FILTER_GROUP_STR = "uriRelativeFilterGroup"; + + /** + * Value to indicate that the group match is allowed. + */ + public static final int ACTION_ALLOW = 0; + /** + * Value to indicate that the group match is blocked. + */ + public static final int ACTION_BLOCK = 1; + + /** @hide */ + @IntDef(value = { + ACTION_ALLOW, + ACTION_BLOCK + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Action {} + + private final @Action int mAction; + private final ArraySet<UriRelativeFilter> mUriRelativeFilters = new ArraySet<>(); + + /** + * New UriRelativeFilterGroup that matches a Intent data. + * + * @param action Whether this matching group should be allowed or disallowed. + */ + public UriRelativeFilterGroup(@Action int action) { + mAction = action; + } + + /** @hide */ + public UriRelativeFilterGroup(XmlPullParser parser) throws XmlPullParserException, IOException { + mAction = Integer.parseInt(parser.getAttributeValue(null, ALLOW_STR)); + + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG + || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG + || type == XmlPullParser.TEXT) { + continue; + } + + String tagName = parser.getName(); + if (tagName.equals(UriRelativeFilter.URI_RELATIVE_FILTER_STR)) { + addUriRelativeFilter(new UriRelativeFilter(parser)); + } else { + Log.w("IntentFilter", "Unknown tag parsing IntentFilter: " + tagName); + } + XmlUtils.skipCurrentTag(parser); + } + } + + /** + * Return {@link UriRelativeFilterGroup#ACTION_ALLOW} if a URI is allowed when matched + * and {@link UriRelativeFilterGroup#ACTION_BLOCK} if a URI is blacked when matched. + */ + public @Action int getAction() { + return mAction; + } + + /** + * Add a filter to the group. + */ + public void addUriRelativeFilter(@NonNull UriRelativeFilter uriRelativeFilter) { + Objects.requireNonNull(uriRelativeFilter); + if (!CollectionUtils.contains(mUriRelativeFilters, uriRelativeFilter)) { + mUriRelativeFilters.add(uriRelativeFilter); + } + } + + /** + * Returns a unmodifiable view of the UriRelativeFilters list in this group. + */ + @NonNull + public Collection<UriRelativeFilter> getUriRelativeFilters() { + return Collections.unmodifiableCollection(mUriRelativeFilters); + } + + /** + * Match all URI filter in this group against {@link Intent#getData()}. + * + * @param data The full data string to match against, as supplied in + * Intent.data. + * @return true if all filters match. + */ + public boolean matchData(@NonNull Uri data) { + if (mUriRelativeFilters.size() == 0) { + return false; + } + for (UriRelativeFilter filter : mUriRelativeFilters) { + if (!filter.matchData(data)) { + return false; + } + } + return true; + } + + /** @hide */ + public void dumpDebug(ProtoOutputStream proto, long fieldId) { + long token = proto.start(fieldId); + proto.write(UriRelativeFilterGroupProto.ACTION, mAction); + Iterator<UriRelativeFilter> it = mUriRelativeFilters.iterator(); + while (it.hasNext()) { + it.next().dumpDebug(proto, UriRelativeFilterGroupProto.URI_RELATIVE_FILTERS); + } + proto.end(token); + } + + /** @hide */ + public void writeToXml(XmlSerializer serializer) throws IOException { + serializer.startTag(null, URI_RELATIVE_FILTER_GROUP_STR); + serializer.attribute(null, ALLOW_STR, Integer.toString(mAction)); + Iterator<UriRelativeFilter> it = mUriRelativeFilters.iterator(); + while (it.hasNext()) { + UriRelativeFilter filter = it.next(); + filter.writeToXml(serializer); + } + serializer.endTag(null, URI_RELATIVE_FILTER_GROUP_STR); + } + + @Override + public String toString() { + return "UriRelativeFilterGroup { allow = " + mAction + + ", uri_filters = " + mUriRelativeFilters + ", }"; + } + + /** @hide */ + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mAction); + final int n = mUriRelativeFilters.size(); + if (n > 0) { + dest.writeInt(n); + Iterator<UriRelativeFilter> it = mUriRelativeFilters.iterator(); + while (it.hasNext()) { + it.next().writeToParcel(dest, flags); + } + } else { + dest.writeInt(0); + } + } + + /** @hide */ + UriRelativeFilterGroup(@NonNull Parcel src) { + mAction = src.readInt(); + final int n = src.readInt(); + for (int i = 0; i < n; i++) { + mUriRelativeFilters.add(new UriRelativeFilter(src)); + } + } +} diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index c24a974497c4..caff4576c9c3 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -110,6 +110,14 @@ flag { } flag { + name: "relative_reference_intent_filters" + namespace: "package_manager_service" + description: "Feature flag to enable relative reference intent filters" + bug: "307556883" + is_fixed_read_only: true +} + +flag { name: "fix_duplicated_flags" namespace: "package_manager_service" description: "Feature flag to fix duplicated PackageManager flag values" diff --git a/core/java/android/os/PatternMatcher.java b/core/java/android/os/PatternMatcher.java index b5425b43fe7e..79a2c59d6a03 100644 --- a/core/java/android/os/PatternMatcher.java +++ b/core/java/android/os/PatternMatcher.java @@ -16,9 +16,12 @@ package android.os; +import android.annotation.IntDef; import android.util.Log; import android.util.proto.ProtoOutputStream; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Arrays; /** @@ -68,6 +71,17 @@ public class PatternMatcher implements Parcelable { */ public static final int PATTERN_SUFFIX = 4; + /** @hide */ + @IntDef(value = { + PATTERN_LITERAL, + PATTERN_PREFIX, + PATTERN_SIMPLE_GLOB, + PATTERN_ADVANCED_GLOB, + PATTERN_SUFFIX, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface PatternType {} + // token types for advanced matching private static final int TOKEN_TYPE_LITERAL = 0; private static final int TOKEN_TYPE_ANY = 1; diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java index c6683cfc8331..05728eee174f 100644 --- a/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java @@ -18,9 +18,13 @@ package com.android.internal.pm.pkg.component; import static com.android.internal.pm.pkg.parsing.ParsingUtils.ANDROID_RES_NAMESPACE; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.content.Intent; import android.content.IntentFilter; +import android.content.UriRelativeFilter; +import android.content.UriRelativeFilterGroup; +import android.content.pm.Flags; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; import android.content.res.Resources; @@ -132,6 +136,11 @@ public class ParsedIntentInfoUtils { case "data": result = parseData(intentInfo, res, parser, allowGlobs, input); break; + case "uri-relative-filter-group": + if (Flags.relativeReferenceIntentFilters()) { + result = parseRelRefGroup(intentInfo, pkg, res, parser, allowGlobs, input); + break; + } default: result = ParsingUtils.unknownTag("<intent-filter>", pkg, parser, input); break; @@ -163,6 +172,197 @@ public class ParsedIntentInfoUtils { } @NonNull + @FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS) + private static ParseResult<ParsedIntentInfo> parseRelRefGroup(ParsedIntentInfo intentInfo, + ParsingPackage pkg, Resources res, XmlResourceParser parser, boolean allowGlobs, + ParseInput input) throws XmlPullParserException, IOException { + IntentFilter intentFilter = intentInfo.getIntentFilter(); + TypedArray sa = res.obtainAttributes(parser, + R.styleable.AndroidManifestUriRelativeFilterGroup); + UriRelativeFilterGroup group; + try { + int action = UriRelativeFilterGroup.ACTION_ALLOW; + if (!sa.getBoolean(R.styleable.AndroidManifestUriRelativeFilterGroup_allow, true)) { + action = UriRelativeFilterGroup.ACTION_BLOCK; + } + group = new UriRelativeFilterGroup(action); + } finally { + sa.recycle(); + } + final int depth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG + || parser.getDepth() > depth)) { + if (type != XmlPullParser.START_TAG) { + continue; + } + + final ParseResult result; + String nodeName = parser.getName(); + switch (nodeName) { + case "data": + result = parseRelRefGroupData(group, res, parser, allowGlobs, input); + break; + default: + result = ParsingUtils.unknownTag("<uri-relative-filter-group>", + pkg, parser, input); + break; + } + + if (result.isError()) { + return input.error(result); + } + } + + if (group.getUriRelativeFilters().size() > 0) { + intentFilter.addUriRelativeFilterGroup(group); + } + return input.success(null); + } + + @NonNull + @FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS) + private static ParseResult<ParsedIntentInfo> parseRelRefGroupData(UriRelativeFilterGroup group, + Resources res, XmlResourceParser parser, boolean allowGlobs, ParseInput input) { + TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestData); + try { + String str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_path, 0); + if (str != null) { + group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.PATH, + PatternMatcher.PATTERN_LITERAL, str)); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_pathPrefix, 0); + if (str != null) { + group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.PATH, + PatternMatcher.PATTERN_PREFIX, str)); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_pathPattern, 0); + if (str != null) { + if (!allowGlobs) { + return input.error( + "pathPattern not allowed here; path must be literal"); + } + group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.PATH, + PatternMatcher.PATTERN_SIMPLE_GLOB, str)); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_pathAdvancedPattern, 0); + if (str != null) { + if (!allowGlobs) { + return input.error( + "pathAdvancedPattern not allowed here; path must be literal"); + } + group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.PATH, + PatternMatcher.PATTERN_ADVANCED_GLOB, str)); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_pathSuffix, 0); + if (str != null) { + group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.PATH, + PatternMatcher.PATTERN_SUFFIX, str)); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_fragment, 0); + if (str != null) { + group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.FRAGMENT, + PatternMatcher.PATTERN_LITERAL, str)); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_fragmentPrefix, 0); + if (str != null) { + group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.FRAGMENT, + PatternMatcher.PATTERN_PREFIX, str)); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_fragmentPattern, 0); + if (str != null) { + if (!allowGlobs) { + return input.error( + "fragmentPattern not allowed here; fragment must be literal"); + } + group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.FRAGMENT, + PatternMatcher.PATTERN_SIMPLE_GLOB, str)); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_fragmentAdvancedPattern, 0); + if (str != null) { + if (!allowGlobs) { + return input.error( + "fragmentAdvancedPattern not allowed here; fragment must be literal"); + } + group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.FRAGMENT, + PatternMatcher.PATTERN_ADVANCED_GLOB, str)); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_fragmentSuffix, 0); + if (str != null) { + group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.FRAGMENT, + PatternMatcher.PATTERN_SUFFIX, str)); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_query, 0); + if (str != null) { + group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.QUERY, + PatternMatcher.PATTERN_LITERAL, str)); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_queryPrefix, 0); + if (str != null) { + group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.QUERY, + PatternMatcher.PATTERN_PREFIX, str)); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_queryPattern, 0); + if (str != null) { + if (!allowGlobs) { + return input.error( + "queryPattern not allowed here; query must be literal"); + } + group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.QUERY, + PatternMatcher.PATTERN_SIMPLE_GLOB, str)); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_queryAdvancedPattern, 0); + if (str != null) { + if (!allowGlobs) { + return input.error( + "queryAdvancedPattern not allowed here; query must be literal"); + } + group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.QUERY, + PatternMatcher.PATTERN_ADVANCED_GLOB, str)); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_querySuffix, 0); + if (str != null) { + group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.QUERY, + PatternMatcher.PATTERN_SUFFIX, str)); + } + + return input.success(null); + } finally { + sa.recycle(); + } + } + + @NonNull private static ParseResult<ParsedIntentInfo> parseData(ParsedIntentInfo intentInfo, Resources resources, XmlResourceParser parser, boolean allowGlobs, ParseInput input) { IntentFilter intentFilter = intentInfo.getIntentFilter(); diff --git a/core/proto/android/content/intent.proto b/core/proto/android/content/intent.proto index 75e29082508b..1d1f88b01838 100644 --- a/core/proto/android/content/intent.proto +++ b/core/proto/android/content/intent.proto @@ -66,7 +66,7 @@ message IntentProto { optional string identifier = 13 [ (.android.privacy).dest = DEST_EXPLICIT ]; } -// Next Tag: 12 +// Next Tag: 14 message IntentFilterProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; @@ -89,6 +89,7 @@ message IntentFilterProto { optional bool get_auto_verify = 10; repeated string mime_groups = 11; optional android.os.PersistableBundleProto extras = 12; + repeated UriRelativeFilterGroupProto uri_relative_filter_groups = 13; } message AuthorityEntryProto { @@ -98,3 +99,23 @@ message AuthorityEntryProto { optional bool wild = 2; optional int32 port = 3; } + +message UriRelativeFilterGroupProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + enum Action { + ACTION_ALLOW = 0; + ACTION_BLOCK = 1; + } + + optional Action action = 1; + repeated UriRelativeFilterProto uri_relative_filters = 2; +} + +message UriRelativeFilterProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + required int32 uri_part = 1; + required int32 pattern_type = 2; + required string filter = 3; +} diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 35276bf8ead2..6884fc0057d9 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -3438,6 +3438,20 @@ <!-- Attributes that can be supplied in an AndroidManifest.xml <code>data</code> tag, a child of the {@link #AndroidManifestIntentFilter intent-filter} tag, describing + a group matching rule consisting of one or more + {@link #AndroidManifestData data} tags that must all match. This + tag can be specified multiple times to create multiple groups that + will be matched in the order they are defined. --> + <declare-styleable name="AndroidManifestUriRelativeFilterGroup" + parent="AndroidManifestIntentFilter"> + <!-- Specify if this group is allow rule or disallow rule. If this + attribute is not specified then it is assumed to be true --> + <attr name="allow" format="boolean"/> + </declare-styleable> + + <!-- Attributes that can be supplied in an AndroidManifest.xml + <code>data</code> tag, a child of the + {@link #AndroidManifestIntentFilter intent-filter} tag, describing the types of data that match. This tag can be specified multiple times to supply multiple data options, as described in the {@link android.content.IntentFilter} class. Note that all such @@ -3445,7 +3459,8 @@ <code><data android:scheme="myscheme" android:host="me.com" /></code> is equivalent to <code><data android:scheme="myscheme" /> <data android:host="me.com" /></code>. --> - <declare-styleable name="AndroidManifestData" parent="AndroidManifestIntentFilter"> + <declare-styleable name="AndroidManifestData" + parent="AndroidManifestIntentFilter AndroidManifestUriRelativeFilterGroup"> <!-- Specify a MIME type that is handled, as per {@link android.content.IntentFilter#addDataType IntentFilter.addDataType()}. @@ -3549,6 +3564,70 @@ IntentFilter.addDataPath()} with {@link android.os.PatternMatcher#PATTERN_SUFFIX}. --> <attr name="pathSuffix" /> + <!-- Specify a URI query that must exactly match, as a + {@link android.content.UriRelativeFilter UriRelativeFilter} with + {@link android.os.PatternMatcher#PATTERN_LITERAL}. --> + <attr name="query" format="string" /> + <!-- Specify a URI query that must be a prefix to match, as a + {@link android.content.UriRelativeFilter UriRelativeFilter} with + {@link android.os.PatternMatcher#PATTERN_PREFIX}. --> + <attr name="queryPrefix" format="string" /> + <!-- Specify a URI query that matches a simple pattern, as a + {@link android.content.UriRelativeFilter UriRelativeFilter} with + {@link android.os.PatternMatcher#PATTERN_SIMPLE_GLOB}. + Note that because '\' is used as an escape character when + reading the string from XML (before it is parsed as a pattern), + you will need to double-escape: for example a literal "*" would + be written as "\\*" and a literal "\" would be written as + "\\\\". This is basically the same as what you would need to + write if constructing the string in Java code. --> + <attr name="queryPattern" format="string" /> + <!-- Specify a URI query that matches an advanced pattern, as a + {@link android.content.UriRelativeFilter UriRelativeFilter} with + {@link android.os.PatternMatcher#PATTERN_ADVANCED_GLOB}. + Note that because '\' is used as an escape character when + reading the string from XML (before it is parsed as a pattern), + you will need to double-escape: for example a literal "*" would + be written as "\\*" and a literal "\" would be written as + "\\\\". This is basically the same as what you would need to + write if constructing the string in Java code. --> + <attr name="queryAdvancedPattern" format="string" /> + <!-- Specify a URI query that must be a suffix to match, as a + {@link android.content.UriRelativeFilter UriRelativeFilter} with + {@link android.os.PatternMatcher#PATTERN_SUFFIX}. --> + <attr name="querySuffix" format="string" /> + <!-- Specify a URI fragment that must exactly match, as a + {@link android.content.UriRelativeFilter UriRelativeFilter} with + {@link android.os.PatternMatcher#PATTERN_LITERAL}. --> + <attr name="fragment" format="string" /> + <!-- Specify a URI fragment that must be a prefix to match, as a + {@link android.content.UriRelativeFilter UriRelativeFilter} with + {@link android.os.PatternMatcher#PATTERN_PREFIX}. --> + <attr name="fragmentPrefix" format="string" /> + <!-- Specify a URI fragment that matches a simple pattern, as a + {@link android.content.UriRelativeFilter UriRelativeFilter} with + {@link android.os.PatternMatcher#PATTERN_SIMPLE_GLOB}. + Note that because '\' is used as an escape character when + reading the string from XML (before it is parsed as a pattern), + you will need to double-escape: for example a literal "*" would + be written as "\\*" and a literal "\" would be written as + "\\\\". This is basically the same as what you would need to + write if constructing the string in Java code. --> + <attr name="fragmentPattern" format="string" /> + <!-- Specify a URI fragment that matches an advanced pattern, as a + {@link android.content.UriRelativeFilter UriRelativeFilter} with + {@link android.os.PatternMatcher#PATTERN_ADVANCED_GLOB}. + Note that because '\' is used as an escape character when + reading the string from XML (before it is parsed as a pattern), + you will need to double-escape: for example a literal "*" would + be written as "\\*" and a literal "\" would be written as + "\\\\". This is basically the same as what you would need to + write if constructing the string in Java code. --> + <attr name="fragmentAdvancedPattern" format="string" /> + <!-- Specify a URI fragment that must be a suffix to match, as a + {@link android.content.UriRelativeFilter UriRelativeFilter} with + {@link android.os.PatternMatcher#PATTERN_SUFFIX}. --> + <attr name="fragmentSuffix" format="string" /> </declare-styleable> <!-- Attributes that can be supplied in an AndroidManifest.xml diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index b8fc052a2fa9..830e99ca907b 100644 --- a/core/res/res/values/public-staging.xml +++ b/core/res/res/values/public-staging.xml @@ -123,6 +123,26 @@ <public name="featureFlag"/> <!-- @FlaggedApi("android.multiuser.enable_system_user_only_for_services_and_providers") --> <public name="systemUserOnly"/> + <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") --> + <public name="allow"/> + <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") --> + <public name="query"/> + <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") --> + <public name="queryPrefix"/> + <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") --> + <public name="queryPattern"/> + <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") --> + <public name="queryAdvancedPattern"/> + <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") --> + <public name="querySuffix"/> + <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") --> + <public name="fragmentPrefix"/> + <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") --> + <public name="fragmentPattern"/> + <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") --> + <public name="fragmentAdvancedPattern"/> + <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") --> + <public name="fragmentSuffix"/> </staging-public-group> <staging-public-group type="id" first-id="0x01bc0000"> diff --git a/nfc/jarjar-rules.txt b/nfc/jarjar-rules.txt index 4cd652d6af7f..99ae14414dad 100644 --- a/nfc/jarjar-rules.txt +++ b/nfc/jarjar-rules.txt @@ -4,6 +4,7 @@ rule android.content.ComponentNameProto* com.android.nfc.x.@0 rule android.content.IntentProto* com.android.nfc.x.@0 rule android.content.IntentFilterProto* com.android.nfc.x.@0 rule android.content.AuthorityEntryProto* com.android.nfc.x.@0 +rule android.content.UriRelativeFilter* com.android.nfc.x.@0 rule android.nfc.cardemulation.AidGroupProto* com.android.nfc.x.@0 rule android.nfc.cardemulation.ApduServiceInfoProto* com.android.nfc.x.@0 rule android.nfc.cardemulation.NfcFServiceInfoProto* com.android.nfc.x.@0 diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp index 3aca1cafbf75..f8accc309931 100644 --- a/services/tests/PackageManagerServiceTests/server/Android.bp +++ b/services/tests/PackageManagerServiceTests/server/Android.bp @@ -103,6 +103,7 @@ android_test { ":PackageParserTestApp4", ":PackageParserTestApp5", ":PackageParserTestApp6", + ":PackageParserTestApp7", ], resource_zips: [":PackageManagerServiceServerTests_apks_as_resources"], diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java index 71f5c754f22f..a0e0e1ef36ee 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java @@ -15,6 +15,17 @@ */ package com.android.server.pm; +import static android.content.UriRelativeFilter.PATH; +import static android.content.UriRelativeFilter.QUERY; +import static android.content.UriRelativeFilter.FRAGMENT; +import static android.content.UriRelativeFilterGroup.ACTION_ALLOW; +import static android.content.UriRelativeFilterGroup.ACTION_BLOCK; +import static android.os.PatternMatcher.PATTERN_ADVANCED_GLOB; +import static android.os.PatternMatcher.PATTERN_LITERAL; +import static android.os.PatternMatcher.PATTERN_PREFIX; +import static android.os.PatternMatcher.PATTERN_SIMPLE_GLOB; +import static android.os.PatternMatcher.PATTERN_SUFFIX; + import static com.android.internal.pm.permission.CompatibilityPermissionInfo.COMPAT_PERMS; import static com.google.common.truth.Truth.assertThat; @@ -36,11 +47,15 @@ import static java.util.stream.Collectors.toList; import android.annotation.NonNull; import android.content.Context; +import android.content.IntentFilter; +import android.content.UriRelativeFilter; +import android.content.UriRelativeFilterGroup; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ConfigurationInfo; import android.content.pm.FeatureGroupInfo; import android.content.pm.FeatureInfo; +import android.content.pm.Flags; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.Property; import android.content.pm.ServiceInfo; @@ -50,6 +65,9 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.util.ArraySet; import androidx.annotation.Nullable; @@ -106,6 +124,7 @@ import java.lang.reflect.Field; import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; @@ -123,6 +142,9 @@ public class PackageParserTest { @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + private File mTmpDir; private static final File FRAMEWORK = new File("/system/framework/framework-res.apk"); private static final String TEST_APP1_APK = "PackageParserTestApp1.apk"; @@ -131,6 +153,7 @@ public class PackageParserTest { private static final String TEST_APP4_APK = "PackageParserTestApp4.apk"; private static final String TEST_APP5_APK = "PackageParserTestApp5.apk"; private static final String TEST_APP6_APK = "PackageParserTestApp6.apk"; + private static final String TEST_APP7_APK = "PackageParserTestApp7.apk"; private static final String PACKAGE_NAME = "com.android.servicestests.apps.packageparserapp"; @Before @@ -375,6 +398,87 @@ public class PackageParserTest { assertNotEquals("$automotive", actualDisplayCategory); } + @Test + @RequiresFlagsEnabled(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS) + public void testParseUriRelativeFilterGroups() throws Exception { + final File testFile = extractFile(TEST_APP7_APK); + try { + final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false); + final List<ParsedActivity> activities = pkg.getActivities(); + final List<ParsedIntentInfo> intents = activities.get(0).getIntents(); + final IntentFilter intentFilter = intents.get(0).getIntentFilter(); + assertEquals(7, intentFilter.countUriRelativeFilterGroups()); + + UriRelativeFilterGroup group = intentFilter.getUriRelativeFilterGroup(0); + Collection<UriRelativeFilter> filters = group.getUriRelativeFilters(); + assertEquals(ACTION_BLOCK, group.getAction()); + assertEquals(3, filters.size()); + assertTrue(filters.contains(new UriRelativeFilter(PATH, PATTERN_PREFIX, "/gizmos"))); + assertTrue(filters.contains(new UriRelativeFilter(QUERY, PATTERN_SIMPLE_GLOB, + ".*query=string.*"))); + assertTrue(filters.contains(new UriRelativeFilter(FRAGMENT, PATTERN_LITERAL, + "fragment"))); + + group = intentFilter.getUriRelativeFilterGroup(1); + filters = group.getUriRelativeFilters(); + assertEquals(ACTION_ALLOW, group.getAction()); + assertEquals(2, filters.size()); + assertTrue(filters.contains(new UriRelativeFilter(QUERY, PATTERN_LITERAL, + "query=string"))); + assertTrue(filters.contains(new UriRelativeFilter(FRAGMENT, PATTERN_SUFFIX, + "fragment"))); + + group = intentFilter.getUriRelativeFilterGroup(2); + filters = group.getUriRelativeFilters(); + assertEquals(ACTION_ALLOW, group.getAction()); + assertTrue(filters.contains(new UriRelativeFilter(PATH, PATTERN_LITERAL, "/gizmos"))); + assertTrue(filters.contains(new UriRelativeFilter(QUERY, PATTERN_LITERAL, + ".*query=string.*"))); + assertTrue(filters.contains(new UriRelativeFilter(FRAGMENT, PATTERN_LITERAL, + "fragment"))); + + group = intentFilter.getUriRelativeFilterGroup(3); + filters = group.getUriRelativeFilters(); + assertEquals(ACTION_ALLOW, group.getAction()); + assertTrue(filters.contains(new UriRelativeFilter(PATH, PATTERN_PREFIX, "/gizmos"))); + assertTrue(filters.contains(new UriRelativeFilter(QUERY, PATTERN_PREFIX, + ".*query=string.*"))); + assertTrue(filters.contains(new UriRelativeFilter(FRAGMENT, PATTERN_PREFIX, + "fragment"))); + + group = intentFilter.getUriRelativeFilterGroup(4); + filters = group.getUriRelativeFilters(); + assertEquals(ACTION_ALLOW, group.getAction()); + assertTrue(filters.contains(new UriRelativeFilter(PATH, PATTERN_SIMPLE_GLOB, + "/gizmos"))); + assertTrue(filters.contains(new UriRelativeFilter(QUERY, PATTERN_SIMPLE_GLOB, + ".*query=string.*"))); + assertTrue(filters.contains(new UriRelativeFilter(FRAGMENT, PATTERN_SIMPLE_GLOB, + "fragment"))); + + group = intentFilter.getUriRelativeFilterGroup(5); + filters = group.getUriRelativeFilters(); + assertEquals(ACTION_ALLOW, group.getAction()); + assertTrue(filters.contains(new UriRelativeFilter(PATH, PATTERN_ADVANCED_GLOB, + "/gizmos"))); + assertTrue(filters.contains(new UriRelativeFilter(QUERY, PATTERN_ADVANCED_GLOB, + ".*query=string.*"))); + assertTrue(filters.contains(new UriRelativeFilter(FRAGMENT, PATTERN_ADVANCED_GLOB, + "fragment"))); + + group = intentFilter.getUriRelativeFilterGroup(6); + filters = group.getUriRelativeFilters(); + assertEquals(ACTION_ALLOW, group.getAction()); + assertTrue(filters.contains(new UriRelativeFilter(PATH, PATTERN_SUFFIX, "/gizmos"))); + assertTrue(filters.contains(new UriRelativeFilter(QUERY, PATTERN_SUFFIX, + ".*query=string.*"))); + assertTrue(filters.contains(new UriRelativeFilter(FRAGMENT, PATTERN_SUFFIX, + "fragment"))); + } finally { + testFile.delete(); + } + } + private static final int PROPERTY_TYPE_BOOLEAN = 1; private static final int PROPERTY_TYPE_FLOAT = 2; private static final int PROPERTY_TYPE_INTEGER = 3; diff --git a/services/tests/servicestests/test-apps/PackageParserApp/Android.bp b/services/tests/servicestests/test-apps/PackageParserApp/Android.bp index 3e78f9a9674a..131b380d9215 100644 --- a/services/tests/servicestests/test-apps/PackageParserApp/Android.bp +++ b/services/tests/servicestests/test-apps/PackageParserApp/Android.bp @@ -102,3 +102,17 @@ android_test_helper_app { resource_dirs: ["res"], manifest: "AndroidManifestApp6.xml", } + +android_test_helper_app { + name: "PackageParserTestApp7", + sdk_version: "current", + srcs: ["**/*.java"], + dex_preopt: { + enabled: false, + }, + optimize: { + enabled: false, + }, + resource_dirs: ["res"], + manifest: "AndroidManifestApp7.xml", +} diff --git a/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp7.xml b/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp7.xml new file mode 100644 index 000000000000..cb87a48eb524 --- /dev/null +++ b/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp7.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.servicestests.apps.packageparserapp" > + + <application> + <activity android:name=".TestActivity" + android:exported="true" > + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + <data android:scheme="http" + android:host="www.example.com" /> + <uri-relative-filter-group android:allow="false"> + <data android:pathPrefix="/gizmos" /> + <data android:queryPattern=".*query=string.*" /> + <data android:fragment="fragment" /> + </uri-relative-filter-group> + <uri-relative-filter-group> + <data android:query="query=string" /> + <data android:fragmentSuffix="fragment" /> + </uri-relative-filter-group> + <uri-relative-filter-group> + <data android:path="/gizmos" /> + <data android:query=".*query=string.*" /> + <data android:fragment="fragment" /> + </uri-relative-filter-group> + <uri-relative-filter-group> + <data android:pathPrefix="/gizmos" /> + <data android:queryPrefix=".*query=string.*" /> + <data android:fragmentPrefix="fragment" /> + </uri-relative-filter-group> + <uri-relative-filter-group> + <data android:pathPattern="/gizmos" /> + <data android:queryPattern=".*query=string.*" /> + <data android:fragmentPattern="fragment" /> + </uri-relative-filter-group> + <uri-relative-filter-group> + <data android:pathAdvancedPattern="/gizmos" /> + <data android:queryAdvancedPattern=".*query=string.*" /> + <data android:fragmentAdvancedPattern="fragment" /> + </uri-relative-filter-group> + <uri-relative-filter-group> + <data android:pathSuffix="/gizmos" /> + <data android:querySuffix=".*query=string.*" /> + <data android:fragmentSuffix="fragment" /> + </uri-relative-filter-group> + </intent-filter> + </activity> + </application> + +</manifest>
\ No newline at end of file diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index 0b16e2c7efe4..d03f97e28156 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -415,6 +415,8 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, android::IDiagn intent_filter_action["action"].Action(RequiredNameIsNotEmpty); intent_filter_action["category"].Action(RequiredNameIsNotEmpty); intent_filter_action["data"]; + intent_filter_action["uri-relative-filter-group"]; + intent_filter_action["uri-relative-filter-group"]["data"]; // Common <meta-data> actions. xml::XmlNodeAction meta_data_action; |