summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author William Loh <wloh@google.com> 2024-01-26 18:41:35 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-01-26 18:41:35 +0000
commite8ae59a7f2862ad2fa392e19bb5d0821e62546e1 (patch)
tree26511785279b825e082116287be084e32447ef70
parent9d77c522244106140585f9c0ad790e69393615ad (diff)
parentd31458436119bc9e78477855cfca20f92c818fbc (diff)
Merge "Add <uri-relative-filter-group> to intent filters." into main
-rw-r--r--core/api/current.txt35
-rw-r--r--core/java/android/content/IntentFilter.java140
-rw-r--r--core/java/android/content/UriRelativeFilter.java260
-rw-r--r--core/java/android/content/UriRelativeFilterGroup.java216
-rw-r--r--core/java/android/content/pm/flags.aconfig8
-rw-r--r--core/java/android/os/PatternMatcher.java14
-rw-r--r--core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java200
-rw-r--r--core/proto/android/content/intent.proto23
-rw-r--r--core/res/res/values/attrs_manifest.xml81
-rw-r--r--core/res/res/values/public-staging.xml20
-rw-r--r--nfc/jarjar-rules.txt1
-rw-r--r--services/tests/PackageManagerServiceTests/server/Android.bp1
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java104
-rw-r--r--services/tests/servicestests/test-apps/PackageParserApp/Android.bp14
-rw-r--r--services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp7.xml67
-rw-r--r--tools/aapt2/link/ManifestFixer.cpp2
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>&lt;data android:scheme="myscheme" android:host="me.com" /&gt;</code>
is equivalent to <code>&lt;data android:scheme="myscheme" /&gt;
&lt;data android:host="me.com" /&gt;</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;