summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/system-current.txt8
-rw-r--r--core/java/android/app/BroadcastOptions.java135
-rw-r--r--core/java/android/content/IntentFilter.java488
-rw-r--r--core/proto/android/content/intent.proto2
-rw-r--r--services/core/java/com/android/server/am/BroadcastRecord.java35
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java79
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java144
7 files changed, 852 insertions, 39 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 014c4eee3924..46c49ce4acb8 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -783,13 +783,19 @@ package android.app {
}
public class BroadcastOptions {
+ method public void clearDeliveryGroupMatchingFilter();
+ method public void clearDeliveryGroupMatchingKey();
method public void clearDeliveryGroupPolicy();
method public void clearRequireCompatChange();
+ method @Nullable public android.content.IntentFilter getDeliveryGroupMatchingFilter();
+ method @Nullable public String getDeliveryGroupMatchingKey();
method public int getDeliveryGroupPolicy();
method public boolean isPendingIntentBackgroundActivityLaunchAllowed();
method public static android.app.BroadcastOptions makeBasic();
method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS) public void recordResponseEventWhileInBackground(@IntRange(from=0) long);
method @RequiresPermission(android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND) public void setBackgroundActivityStartsAllowed(boolean);
+ method public void setDeliveryGroupMatchingFilter(@NonNull android.content.IntentFilter);
+ method public void setDeliveryGroupMatchingKey(@NonNull String, @NonNull String);
method public void setDeliveryGroupPolicy(int);
method public void setDontSendToRestrictedApps(boolean);
method public void setPendingIntentBackgroundActivityLaunchAllowed(boolean);
@@ -3218,7 +3224,9 @@ package android.content {
}
public class IntentFilter implements android.os.Parcelable {
+ method @NonNull public final android.os.PersistableBundle getExtras();
method public final int getOrder();
+ method public final void setExtras(@NonNull android.os.PersistableBundle);
method public final void setOrder(int);
}
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index 1777f37202c2..f25e639a9c80 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -66,8 +66,9 @@ public class BroadcastOptions extends ComponentOptions {
private long mIdForResponseEvent;
private @Nullable IntentFilter mRemoveMatchingFilter;
private @DeliveryGroupPolicy int mDeliveryGroupPolicy;
- private @Nullable String mDeliveryGroupKey;
+ private @Nullable String mDeliveryGroupMatchingKey;
private @Nullable BundleMerger mDeliveryGroupExtrasMerger;
+ private @Nullable IntentFilter mDeliveryGroupMatchingFilter;
/**
* Change ID which is invalid.
@@ -206,10 +207,10 @@ public class BroadcastOptions extends ComponentOptions {
"android:broadcast.deliveryGroupPolicy";
/**
- * Corresponds to {@link #setDeliveryGroupKey(String, String)}.
+ * Corresponds to {@link #setDeliveryGroupMatchingKey(String, String)}.
*/
private static final String KEY_DELIVERY_GROUP_KEY =
- "android:broadcast.deliveryGroupKey";
+ "android:broadcast.deliveryGroupMatchingKey";
/**
* Corresponds to {@link #setDeliveryGroupExtrasMerger(BundleMerger)}.
@@ -218,6 +219,12 @@ public class BroadcastOptions extends ComponentOptions {
"android:broadcast.deliveryGroupExtrasMerger";
/**
+ * Corresponds to {@link #setDeliveryGroupMatchingFilter(IntentFilter)}.
+ */
+ private static final String KEY_DELIVERY_GROUP_MATCHING_FILTER =
+ "android:broadcast.deliveryGroupMatchingFilter";
+
+ /**
* The list of delivery group policies which specify how multiple broadcasts belonging to
* the same delivery group has to be handled.
* @hide
@@ -304,9 +311,11 @@ public class BroadcastOptions extends ComponentOptions {
IntentFilter.class);
mDeliveryGroupPolicy = opts.getInt(KEY_DELIVERY_GROUP_POLICY,
DELIVERY_GROUP_POLICY_ALL);
- mDeliveryGroupKey = opts.getString(KEY_DELIVERY_GROUP_KEY);
+ mDeliveryGroupMatchingKey = opts.getString(KEY_DELIVERY_GROUP_KEY);
mDeliveryGroupExtrasMerger = opts.getParcelable(KEY_DELIVERY_GROUP_EXTRAS_MERGER,
BundleMerger.class);
+ mDeliveryGroupMatchingFilter = opts.getParcelable(KEY_DELIVERY_GROUP_MATCHING_FILTER,
+ IntentFilter.class);
}
/**
@@ -733,7 +742,7 @@ public class BroadcastOptions extends ComponentOptions {
/**
* Clears any previously set delivery group policies using
- * {@link #setDeliveryGroupKey(String, String)} and resets the delivery group policy to
+ * {@link #setDeliveryGroupMatchingKey(String, String)} and resets the delivery group policy to
* the default value ({@link #DELIVERY_GROUP_POLICY_ALL}).
*
* @hide
@@ -745,22 +754,92 @@ public class BroadcastOptions extends ComponentOptions {
/**
* Set namespace and key to identify the delivery group that this broadcast belongs to.
- * If no namespace and key is set, then by default {@link Intent#filterEquals(Intent)} will be
- * used to identify the delivery group.
+ *
+ * <p> If {@code namespace} and {@code key} are specified, then another broadcast will be
+ * considered to be in the same delivery group as this iff it has the same {@code namespace}
+ * and {@code key}.
+ *
+ * <p> If neither matching key using this API nor matching filter using
+ * {@link #setDeliveryGroupMatchingFilter(IntentFilter)} is specified, then by default
+ * {@link Intent#filterEquals(Intent)} will be used to identify the delivery group.
*
* @hide
*/
- public void setDeliveryGroupKey(@NonNull String namespace, @NonNull String key) {
+ @SystemApi
+ public void setDeliveryGroupMatchingKey(@NonNull String namespace, @NonNull String key) {
Preconditions.checkArgument(!namespace.contains("/"),
"namespace should not contain '/'");
Preconditions.checkArgument(!key.contains("/"),
"key should not contain '/'");
- mDeliveryGroupKey = namespace + "/" + key;
+ mDeliveryGroupMatchingKey = namespace + "/" + key;
}
- /** @hide */
- public String getDeliveryGroupKey() {
- return mDeliveryGroupKey;
+ /**
+ * Return the namespace and key that is used to identify the delivery group that this
+ * broadcast belongs to.
+ *
+ * @return the delivery group namespace and key that was previously set using
+ * {@link #setDeliveryGroupMatchingKey(String, String)}, concatenated with a {@code /}.
+ * @hide
+ */
+ @SystemApi
+ @Nullable
+ public String getDeliveryGroupMatchingKey() {
+ return mDeliveryGroupMatchingKey;
+ }
+
+ /**
+ * Clears the namespace and key that was previously set using
+ * {@link #setDeliveryGroupMatchingKey(String, String)}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public void clearDeliveryGroupMatchingKey() {
+ mDeliveryGroupMatchingKey = null;
+ }
+
+ /**
+ * Set the {@link IntentFilter} object to identify the delivery group that this broadcast
+ * belongs to.
+ *
+ * <p> If a {@code matchingFilter} is specified, then another broadcast will be considered
+ * to be in the same delivery group as this iff the {@code matchingFilter} matches it's intent.
+ *
+ * <p> If neither matching key using {@link #setDeliveryGroupMatchingKey(String, String)} nor
+ * matching filter using this API is specified, then by default
+ * {@link Intent#filterEquals(Intent)} will be used to identify the delivery group.
+ *
+ * @hide
+ */
+ @SystemApi
+ public void setDeliveryGroupMatchingFilter(@NonNull IntentFilter matchingFilter) {
+ mDeliveryGroupMatchingFilter = Objects.requireNonNull(matchingFilter);
+ }
+
+ /**
+ * Return the {@link IntentFilter} object that is used to identify the delivery group
+ * that this broadcast belongs to.
+ *
+ * @return the {@link IntentFilter} object that was previously set using
+ * {@link #setDeliveryGroupMatchingFilter(IntentFilter)}.
+ * @hide
+ */
+ @SystemApi
+ @Nullable
+ public IntentFilter getDeliveryGroupMatchingFilter() {
+ return mDeliveryGroupMatchingFilter;
+ }
+
+ /**
+ * Clears the {@link IntentFilter} object that was previously set using
+ * {@link #setDeliveryGroupMatchingFilter(IntentFilter)}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public void clearDeliveryGroupMatchingFilter() {
+ mDeliveryGroupMatchingFilter = null;
}
/**
@@ -773,16 +852,33 @@ public class BroadcastOptions extends ComponentOptions {
* @hide
*/
public void setDeliveryGroupExtrasMerger(@NonNull BundleMerger extrasMerger) {
- Preconditions.checkNotNull(extrasMerger);
- mDeliveryGroupExtrasMerger = extrasMerger;
+ mDeliveryGroupExtrasMerger = Objects.requireNonNull(extrasMerger);
}
- /** @hide */
- public @Nullable BundleMerger getDeliveryGroupExtrasMerger() {
+ /**
+ * Return the {@link BundleMerger} that specifies how to merge the extras data from
+ * broadcasts in a delivery group.
+ *
+ * @return the {@link BundleMerger} object that was previously set using
+ * {@link #setDeliveryGroupExtrasMerger(BundleMerger)}.
+ * @hide
+ */
+ @Nullable
+ public BundleMerger getDeliveryGroupExtrasMerger() {
return mDeliveryGroupExtrasMerger;
}
/**
+ * Clear the {@link BundleMerger} object that was previously set using
+ * {@link #setDeliveryGroupExtrasMerger(BundleMerger)}.
+ *
+ * @hide
+ */
+ public void clearDeliveryGroupExtrasMerger() {
+ mDeliveryGroupExtrasMerger = null;
+ }
+
+ /**
* Returns the created options as a Bundle, which can be passed to
* {@link android.content.Context#sendBroadcast(android.content.Intent)
* Context.sendBroadcast(Intent)} and related methods.
@@ -837,8 +933,8 @@ public class BroadcastOptions extends ComponentOptions {
if (mDeliveryGroupPolicy != DELIVERY_GROUP_POLICY_ALL) {
b.putInt(KEY_DELIVERY_GROUP_POLICY, mDeliveryGroupPolicy);
}
- if (mDeliveryGroupKey != null) {
- b.putString(KEY_DELIVERY_GROUP_KEY, mDeliveryGroupKey);
+ if (mDeliveryGroupMatchingKey != null) {
+ b.putString(KEY_DELIVERY_GROUP_KEY, mDeliveryGroupMatchingKey);
}
if (mDeliveryGroupPolicy == DELIVERY_GROUP_POLICY_MERGED) {
if (mDeliveryGroupExtrasMerger != null) {
@@ -849,6 +945,9 @@ public class BroadcastOptions extends ComponentOptions {
+ "when delivery group policy is 'MERGED'");
}
}
+ if (mDeliveryGroupMatchingFilter != null) {
+ b.putParcelable(KEY_DELIVERY_GROUP_MATCHING_FILTER, mDeliveryGroupMatchingFilter);
+ }
return b.isEmpty() ? null : b;
}
}
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index 8b6c4dd8497b..bff27d35cc23 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -23,9 +23,11 @@ import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.net.Uri;
import android.os.Build;
+import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PatternMatcher;
+import android.os.PersistableBundle;
import android.text.TextUtils;
import android.util.AndroidException;
import android.util.ArraySet;
@@ -171,6 +173,13 @@ public class IntentFilter implements Parcelable {
private static final String NAME_STR = "name";
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 int[] EMPTY_INT_ARRAY = new int[0];
+ private static final long[] EMPTY_LONG_ARRAY = new long[0];
+ private static final double[] EMPTY_DOUBLE_ARRAY = new double[0];
+ private static final String[] EMPTY_STRING_ARRAY = new String[0];
+ private static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0];
/**
* The filter {@link #setPriority} value at which system high-priority
@@ -267,6 +276,11 @@ public class IntentFilter implements Parcelable {
* that were not in the Intent.
*/
public static final int NO_MATCH_CATEGORY = -4;
+ /**
+ * That filter didn't match due to different extras data.
+ * @hide
+ */
+ public static final int NO_MATCH_EXTRAS = -5;
/**
* HTTP scheme.
@@ -314,6 +328,7 @@ public class IntentFilter implements Parcelable {
private ArrayList<String> mMimeGroups = null;
private boolean mHasStaticPartialTypes = false;
private boolean mHasDynamicPartialTypes = false;
+ private PersistableBundle mExtras = null;
private static final int STATE_VERIFY_AUTO = 0x00000001;
private static final int STATE_NEED_VERIFY = 0x00000010;
@@ -507,6 +522,9 @@ public class IntentFilter implements Parcelable {
if (o.mMimeGroups != null) {
mMimeGroups = new ArrayList<String>(o.mMimeGroups);
}
+ if (o.mExtras != null) {
+ mExtras = new PersistableBundle(o.mExtras);
+ }
mHasStaticPartialTypes = o.mHasStaticPartialTypes;
mHasDynamicPartialTypes = o.mHasDynamicPartialTypes;
mVerifyState = o.mVerifyState;
@@ -1768,6 +1786,423 @@ public class IntentFilter implements Parcelable {
}
/**
+ * Match this filter against an Intent's extras. An intent must
+ * have all the extras specified by the filter with the same values,
+ * for the match to succeed.
+ *
+ * <p> An intent con have other extras in addition to those specified
+ * by the filter and it would not affect whether the match succeeds or not.
+ *
+ * @param extras The extras included in the intent, as returned by
+ * Intent.getExtras().
+ *
+ * @return If all extras match (success), null; else the name of the
+ * first extra that didn't match.
+ *
+ * @hide
+ */
+ private String matchExtras(@Nullable Bundle extras) {
+ if (mExtras == null) {
+ return null;
+ }
+ final Set<String> keys = mExtras.keySet();
+ for (String key : keys) {
+ if (extras == null) {
+ return key;
+ }
+ final Object value = mExtras.get(key);
+ final Object otherValue = extras.get(key);
+ if (otherValue == null || value.getClass() != otherValue.getClass()
+ || !Objects.deepEquals(value, otherValue)) {
+ return key;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Add a new extra name and value to match against. If an extra is included in the filter,
+ * then an Intent must have an extra with the same {@code name} and {@code value} for it to
+ * match.
+ *
+ * <p> Subsequent calls to this method with the same {@code name} will override any previously
+ * set {@code value}.
+ *
+ * @param name the name of the extra to match against.
+ * @param value the value of the extra to match against.
+ *
+ * @hide
+ */
+ public final void addExtra(@NonNull String name, int value) {
+ Objects.requireNonNull(name);
+ if (mExtras == null) {
+ mExtras = new PersistableBundle();
+ }
+ mExtras.putInt(name, value);
+ }
+
+ /**
+ * Return the value of the extra with {@code name} included in the filter.
+ *
+ * @return the value that was previously set using {@link #addExtra(String, int)} or
+ * {@code 0} if no value has been set.
+ * @hide
+ */
+ public final int getIntExtra(@NonNull String name) {
+ Objects.requireNonNull(name);
+ return mExtras == null ? 0 : mExtras.getInt(name);
+ }
+
+ /**
+ * Add a new extra name and value to match against. If an extra is included in the filter,
+ * then an Intent must have an extra with the same {@code name} and {@code value} for it to
+ * match.
+ *
+ * <p> Subsequent calls to this method with the same {@code name} will override any previously
+ * set {@code value}.
+ *
+ * @param name the name of the extra to match against.
+ * @param value the value of the extra to match against.
+ *
+ * @hide
+ */
+ public final void addExtra(@NonNull String name, @NonNull int[] value) {
+ Objects.requireNonNull(name);
+ Objects.requireNonNull(value);
+ if (mExtras == null) {
+ mExtras = new PersistableBundle();
+ }
+ mExtras.putIntArray(name, value);
+ }
+
+ /**
+ * Return the value of the extra with {@code name} included in the filter.
+ *
+ * @return the value that was previously set using {@link #addExtra(String, int[])} or
+ * an empty int array if no value has been set.
+ * @hide
+ */
+ @NonNull
+ public final int[] getIntArrayExtra(@NonNull String name) {
+ Objects.requireNonNull(name);
+ return mExtras == null ? EMPTY_INT_ARRAY : mExtras.getIntArray(name);
+ }
+
+ /**
+ * Add a new extra name and value to match against. If an extra is included in the filter,
+ * then an Intent must have an extra with the same {@code name} and {@code value} for it to
+ * match.
+ *
+ * <p> Subsequent calls to this method with the same {@code name} will override any previously
+ * set {@code value}.
+ *
+ * @param name the name of the extra to match against.
+ * @param value the value of the extra to match against.
+ *
+ * @hide
+ */
+ public final void addExtra(@NonNull String name, long value) {
+ Objects.requireNonNull(name);
+ if (mExtras == null) {
+ mExtras = new PersistableBundle();
+ }
+ mExtras.putLong(name, value);
+ }
+
+ /**
+ * Return the value of the extra with {@code name} included in the filter.
+ *
+ * @return the value that was previously set using {@link #addExtra(String, long)} or
+ * {@code 0L} if no value has been set.
+ * @hide
+ */
+ public final long getLongExtra(@NonNull String name) {
+ Objects.requireNonNull(name);
+ return mExtras == null ? 0L : mExtras.getLong(name);
+ }
+
+ /**
+ * Add a new extra name and value to match against. If an extra is included in the filter,
+ * then an Intent must have an extra with the same {@code name} and {@code value} for it to
+ * match.
+ *
+ * <p> Subsequent calls to this method with the same {@code name} will override any previously
+ * set {@code value}.
+ *
+ * @param name the name of the extra to match against.
+ * @param value the value of the extra to match against.
+ *
+ * @hide
+ */
+ public final void addExtra(@NonNull String name, @NonNull long[] value) {
+ Objects.requireNonNull(name);
+ Objects.requireNonNull(value);
+ if (mExtras == null) {
+ mExtras = new PersistableBundle();
+ }
+ mExtras.putLongArray(name, value);
+ }
+
+ /**
+ * Return the value of the extra with {@code name} included in the filter.
+ *
+ * @return the value that was previously set using {@link #addExtra(String, long[])} or
+ * an empty long array if no value has been set.
+ * @hide
+ */
+ @NonNull
+ public final long[] getLongArrayExtra(@NonNull String name) {
+ Objects.requireNonNull(name);
+ return mExtras == null ? EMPTY_LONG_ARRAY : mExtras.getLongArray(name);
+ }
+
+ /**
+ * Add a new extra name and value to match against. If an extra is included in the filter,
+ * then an Intent must have an extra with the same {@code name} and {@code value} for it to
+ * match.
+ *
+ * <p> Subsequent calls to this method with the same {@code name} will override any previously
+ * set {@code value}.
+ *
+ * @param name the name of the extra to match against.
+ * @param value the value of the extra to match against.
+ *
+ * @hide
+ */
+ public final void addExtra(@NonNull String name, double value) {
+ Objects.requireNonNull(name);
+ if (mExtras == null) {
+ mExtras = new PersistableBundle();
+ }
+ mExtras.putDouble(name, value);
+ }
+
+ /**
+ * Return the value of the extra with {@code name} included in the filter.
+ *
+ * @return the value that was previously set using {@link #addExtra(String, double)} or
+ * {@code 0.0} if no value has been set.
+ * @hide
+ */
+ @NonNull
+ public final double getDoubleExtra(@NonNull String name) {
+ Objects.requireNonNull(name);
+ return mExtras == null ? 0.0 : mExtras.getDouble(name);
+ }
+
+ /**
+ * Add a new extra name and value to match against. If an extra is included in the filter,
+ * then an Intent must have an extra with the same {@code name} and {@code value} for it to
+ * match.
+ *
+ * <p> Subsequent calls to this method with the same {@code name} will override any previously
+ * set {@code value}.
+ *
+ * @param name the name of the extra to match against.
+ * @param value the value of the extra to match against.
+ *
+ * @hide
+ */
+ public final void addExtra(@NonNull String name, @NonNull double[] value) {
+ Objects.requireNonNull(name);
+ Objects.requireNonNull(value);
+ if (mExtras == null) {
+ mExtras = new PersistableBundle();
+ }
+ mExtras.putDoubleArray(name, value);
+ }
+
+ /**
+ * Return the value of the extra with {@code name} included in the filter.
+ *
+ * @return the value that was previously set using {@link #addExtra(String, double[])} or
+ * an empty double array if no value has been set.
+ * @hide
+ */
+ @NonNull
+ public final double[] getDoubleArrayExtra(@NonNull String name) {
+ Objects.requireNonNull(name);
+ return mExtras == null ? EMPTY_DOUBLE_ARRAY : mExtras.getDoubleArray(name);
+ }
+
+ /**
+ * Add a new extra name and value to match against. If an extra is included in the filter,
+ * then an Intent must have an extra with the same {@code name} and {@code value} for it to
+ * match.
+ *
+ * <p> Subsequent calls to this method with the same {@code name} will override any previously
+ * set {@code value}.
+ *
+ * @param name the name of the extra to match against.
+ * @param value the value of the extra to match against.
+ *
+ * @hide
+ */
+ public final void addExtra(@NonNull String name, @NonNull String value) {
+ Objects.requireNonNull(name);
+ Objects.requireNonNull(value);
+ if (mExtras == null) {
+ mExtras = new PersistableBundle();
+ }
+ mExtras.putString(name, value);
+ }
+
+ /**
+ * Return the value of the extra with {@code name} included in the filter.
+ *
+ * @return the value that was previously set using {@link #addExtra(String, String)} or a
+ * {@code null} if no value has been set.
+ * @hide
+ */
+ @Nullable
+ public final String getStringExtra(@NonNull String name) {
+ Objects.requireNonNull(name);
+ return mExtras == null ? null : mExtras.getString(name);
+ }
+
+ /**
+ * Add a new extra name and value to match against. If an extra is included in the filter,
+ * then an Intent must have an extra with the same {@code name} and {@code value} for it to
+ * match.
+ *
+ * <p> Subsequent calls to this method with the same {@code name} will override any previously
+ * set {@code value}.
+ *
+ * @param name the name of the extra to match against.
+ * @param value the value of the extra to match against.
+ *
+ * @hide
+ */
+ public final void addExtra(@NonNull String name, @NonNull String[] value) {
+ Objects.requireNonNull(name);
+ Objects.requireNonNull(value);
+ if (mExtras == null) {
+ mExtras = new PersistableBundle();
+ }
+ mExtras.putStringArray(name, value);
+ }
+
+ /**
+ * Return the value of the extra with {@code name} included in the filter.
+ *
+ * @return the value that was previously set using {@link #addExtra(String, String[])} or
+ * an empty string array if no value has been set.
+ * @hide
+ */
+ @NonNull
+ public final String[] getStringArrayExtra(@NonNull String name) {
+ Objects.requireNonNull(name);
+ return mExtras == null ? EMPTY_STRING_ARRAY : mExtras.getStringArray(name);
+ }
+
+ /**
+ * Add a new extra name and value to match against. If an extra is included in the filter,
+ * then an Intent must have an extra with the same {@code name} and {@code value} for it to
+ * match.
+ *
+ * <p> Subsequent calls to this method with the same {@code name} will override any previously
+ * set {@code value}.
+ *
+ * @param name the name of the extra to match against.
+ * @param value the value of the extra to match against.
+ *
+ * @hide
+ */
+ public final void addExtra(@NonNull String name, boolean value) {
+ Objects.requireNonNull(name);
+ if (mExtras == null) {
+ mExtras = new PersistableBundle();
+ }
+ mExtras.putBoolean(name, value);
+ }
+
+ /**
+ * Return the value of the extra with {@code name} included in the filter.
+ *
+ * @return the value that was previously set using {@link #addExtra(String, boolean)} or
+ * {@code false} if no value has been set.
+ * @hide
+ */
+ public final boolean getBooleanExtra(@NonNull String name) {
+ Objects.requireNonNull(name);
+ return mExtras == null ? false : mExtras.getBoolean(name);
+ }
+
+ /**
+ * Add a new extra name and value to match against. If an extra is included in the filter,
+ * then an Intent must have an extra with the same {@code name} and {@code value} for it to
+ * match.
+ *
+ * <p> Subsequent calls to this method with the same {@code name} will override any previously
+ * set {@code value}.
+ *
+ * @param name the name of the extra to match against.
+ * @param value the value of the extra to match against.
+ *
+ * @hide
+ */
+ public final void addExtra(@NonNull String name, @NonNull boolean[] value) {
+ Objects.requireNonNull(name);
+ Objects.requireNonNull(value);
+ if (mExtras == null) {
+ mExtras = new PersistableBundle();
+ }
+ mExtras.putBooleanArray(name, value);
+ }
+
+ /**
+ * Return the value of the extra with {@code name} included in the filter.
+ *
+ * @return the value that was previously set using {@link #addExtra(String, boolean[])} or
+ * an empty boolean array if no value has been set.
+ * @hide
+ */
+ @NonNull
+ public final boolean[] getBooleanArrayExtra(@NonNull String name) {
+ Objects.requireNonNull(name);
+ return mExtras == null ? EMPTY_BOOLEAN_ARRAY : mExtras.getBooleanArray(name);
+ }
+
+ /**
+ * Returns whether or not an extra with {@code name} is included in the filter.
+ *
+ * @return {@code true} if an extra with {@code name} is included in the filter.
+ * Otherwise {@code false}.
+ * @hide
+ */
+ public final boolean hasExtra(@NonNull String name) {
+ Objects.requireNonNull(name);
+ return mExtras == null ? false : mExtras.containsKey(name);
+ }
+
+ /**
+ * Set the intent extras to match against. For this filter to match an
+ * intent, it must contain all the {@code extras} set.
+ *
+ * <p> Subsequent calls to this method will override any previously set extras.
+ *
+ * @param extras The intent extras to match against.
+ * @hide
+ */
+ @SystemApi
+ public final void setExtras(@NonNull PersistableBundle extras) {
+ mExtras = extras;
+ }
+
+ /**
+ * Return the intent extras included in this filter.
+ *
+ * @return the extras that were previously set using {@link #setExtras(PersistableBundle)} or
+ * an empty {@link PersistableBundle} object if no extras were set.
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ public final PersistableBundle getExtras() {
+ return mExtras == null ? new PersistableBundle() : mExtras;
+ }
+
+ /**
* Return a {@link Predicate} which tests whether this filter matches the
* given <var>intent</var>.
* <p>
@@ -1818,7 +2253,9 @@ public class IntentFilter implements Parcelable {
boolean resolve, String logTag) {
String type = resolve ? intent.resolveType(resolver) : intent.getType();
return match(intent.getAction(), type, intent.getScheme(),
- intent.getData(), intent.getCategories(), logTag);
+ intent.getData(), intent.getCategories(), logTag,
+ false /* supportWildcards */, null /* ignoreActions */,
+ intent.getExtras());
}
/**
@@ -1868,6 +2305,19 @@ public class IntentFilter implements Parcelable {
public final int match(String action, String type, String scheme,
Uri data, Set<String> categories, String logTag, boolean supportWildcards,
@Nullable Collection<String> ignoreActions) {
+ return match(action, type, scheme, data, categories, logTag, supportWildcards,
+ ignoreActions, null /* extras */);
+ }
+
+ /**
+ * Variant of {@link #match(String, String, String, Uri, Set, String, boolean, Collection)}
+ * that supports matching the extra values in the intent.
+ *
+ * @hide
+ */
+ public final int match(String action, String type, String scheme,
+ Uri data, Set<String> categories, String logTag, boolean supportWildcards,
+ @Nullable Collection<String> ignoreActions, @Nullable Bundle extras) {
if (action != null && !matchAction(action, supportWildcards, ignoreActions)) {
if (false) Log.v(
logTag, "No matching action " + action + " for " + this);
@@ -1897,6 +2347,14 @@ public class IntentFilter implements Parcelable {
return NO_MATCH_CATEGORY;
}
+ String extraMismatch = matchExtras(extras);
+ if (extraMismatch != null) {
+ if (false) {
+ Log.v(logTag, "Mismatch for extra key " + extraMismatch + " for " + this);
+ }
+ return NO_MATCH_EXTRAS;
+ }
+
// It would be nice to treat container activities as more
// important than ones that can be embedded, but this is not the way...
if (false) {
@@ -1998,6 +2456,15 @@ public class IntentFilter implements Parcelable {
}
serializer.endTag(null, PATH_STR);
}
+ if (mExtras != null) {
+ serializer.startTag(null, EXTRAS_STR);
+ try {
+ mExtras.saveToXml(serializer);
+ } catch (XmlPullParserException e) {
+ throw new IllegalStateException("Failed to write extras: " + mExtras.toString(), e);
+ }
+ serializer.endTag(null, EXTRAS_STR);
+ }
}
/**
@@ -2124,6 +2591,8 @@ public class IntentFilter implements Parcelable {
} else if ((path=parser.getAttributeValue(null, SUFFIX_STR)) != null) {
addDataPath(path, PatternMatcher.PATTERN_SUFFIX);
}
+ } else if (tagName.equals(EXTRAS_STR)) {
+ mExtras = PersistableBundle.restoreFromXml(parser);
} else {
Log.w("IntentFilter", "Unknown tag parsing IntentFilter: " + tagName);
}
@@ -2187,6 +2656,9 @@ public class IntentFilter implements Parcelable {
proto.write(IntentFilterProto.HAS_PARTIAL_TYPES, hasPartialTypes());
}
proto.write(IntentFilterProto.GET_AUTO_VERIFY, getAutoVerify());
+ if (mExtras != null) {
+ mExtras.dumpDebug(proto, IntentFilterProto.EXTRAS);
+ }
proto.end(token);
}
@@ -2297,6 +2769,11 @@ public class IntentFilter implements Parcelable {
sb.append(prefix); sb.append("AutoVerify="); sb.append(getAutoVerify());
du.println(sb.toString());
}
+ if (mExtras != null) {
+ sb.setLength(0);
+ sb.append(prefix); sb.append("mExtras="); sb.append(mExtras.toString());
+ du.println(sb.toString());
+ }
}
public static final @android.annotation.NonNull Parcelable.Creator<IntentFilter> CREATOR
@@ -2379,6 +2856,12 @@ public class IntentFilter implements Parcelable {
dest.writeInt(getAutoVerify() ? 1 : 0);
dest.writeInt(mInstantAppVisibility);
dest.writeInt(mOrder);
+ if (mExtras != null) {
+ dest.writeInt(1);
+ mExtras.writeToParcel(dest, flags);
+ } else {
+ dest.writeInt(0);
+ }
}
/**
@@ -2459,6 +2942,9 @@ public class IntentFilter implements Parcelable {
setAutoVerify(source.readInt() > 0);
setVisibilityToInstantApp(source.readInt());
mOrder = source.readInt();
+ if (source.readInt() != 0) {
+ mExtras = PersistableBundle.CREATOR.createFromParcel(source);
+ }
}
private boolean hasPartialTypes() {
diff --git a/core/proto/android/content/intent.proto b/core/proto/android/content/intent.proto
index 26e7dbb8cc6f..75e29082508b 100644
--- a/core/proto/android/content/intent.proto
+++ b/core/proto/android/content/intent.proto
@@ -21,6 +21,7 @@ option java_multiple_files = true;
import "frameworks/base/core/proto/android/content/component_name.proto";
import "frameworks/base/core/proto/android/os/patternmatcher.proto";
+import "frameworks/base/core/proto/android/os/persistablebundle.proto";
import "frameworks/base/core/proto/android/privacy.proto";
// Next Tag: 14
@@ -87,6 +88,7 @@ message IntentFilterProto {
optional bool has_partial_types = 9;
optional bool get_auto_verify = 10;
repeated string mime_groups = 11;
+ optional android.os.PersistableBundleProto extras = 12;
}
message AuthorityEntryProto {
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 84d744270e82..100b2db691de 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -37,6 +37,7 @@ import android.app.compat.CompatChanges;
import android.content.ComponentName;
import android.content.IIntentReceiver;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ResolveInfo;
import android.os.Binder;
@@ -870,14 +871,34 @@ final class BroadcastRecord extends Binder {
}
}
- public boolean matchesDeliveryGroup(@NonNull BroadcastRecord other) {
- final String key = (options != null) ? options.getDeliveryGroupKey() : null;
- final String otherKey = (other.options != null)
- ? other.options.getDeliveryGroupKey() : null;
- if (key == null && otherKey == null) {
- return intent.filterEquals(other.intent);
+ boolean matchesDeliveryGroup(@NonNull BroadcastRecord other) {
+ return matchesDeliveryGroup(this, other);
+ }
+
+ private static boolean matchesDeliveryGroup(@NonNull BroadcastRecord newRecord,
+ @NonNull BroadcastRecord oldRecord) {
+ final String newMatchingKey = getDeliveryGroupMatchingKey(newRecord);
+ final String oldMatchingKey = getDeliveryGroupMatchingKey(oldRecord);
+ final IntentFilter newMatchingFilter = getDeliveryGroupMatchingFilter(newRecord);
+ // If neither delivery group key nor matching filter is specified, then use
+ // Intent.filterEquals() to identify the delivery group.
+ if (newMatchingKey == null && oldMatchingKey == null && newMatchingFilter == null) {
+ return newRecord.intent.filterEquals(oldRecord.intent);
+ }
+ if (newMatchingFilter != null && !newMatchingFilter.asPredicate().test(oldRecord.intent)) {
+ return false;
}
- return Objects.equals(key, otherKey);
+ return Objects.equals(newMatchingKey, oldMatchingKey);
+ }
+
+ @Nullable
+ private static String getDeliveryGroupMatchingKey(@NonNull BroadcastRecord record) {
+ return record.options == null ? null : record.options.getDeliveryGroupMatchingKey();
+ }
+
+ @Nullable
+ private static IntentFilter getDeliveryGroupMatchingFilter(@NonNull BroadcastRecord record) {
+ return record.options == null ? null : record.options.getDeliveryGroupMatchingFilter();
}
@Override
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 2f6b07bfb6f7..66e7ec00b6d3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -586,7 +586,7 @@ public class BroadcastQueueModernImplTest {
final BroadcastOptions optionsMusicVolumeChanged = BroadcastOptions.makeBasic();
optionsMusicVolumeChanged.setDeliveryGroupPolicy(
BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
- optionsMusicVolumeChanged.setDeliveryGroupKey("audio",
+ optionsMusicVolumeChanged.setDeliveryGroupMatchingKey("audio",
String.valueOf(AudioManager.STREAM_MUSIC));
final Intent alarmVolumeChanged = new Intent(AudioManager.VOLUME_CHANGED_ACTION);
@@ -595,7 +595,7 @@ public class BroadcastQueueModernImplTest {
final BroadcastOptions optionsAlarmVolumeChanged = BroadcastOptions.makeBasic();
optionsAlarmVolumeChanged.setDeliveryGroupPolicy(
BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
- optionsAlarmVolumeChanged.setDeliveryGroupKey("audio",
+ optionsAlarmVolumeChanged.setDeliveryGroupMatchingKey("audio",
String.valueOf(AudioManager.STREAM_ALARM));
// Halt all processing so that we get a consistent view
@@ -651,7 +651,8 @@ public class BroadcastQueueModernImplTest {
final BroadcastOptions optionsPackageChangedForUid = BroadcastOptions.makeBasic();
optionsPackageChangedForUid.setDeliveryGroupPolicy(
BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED);
- optionsPackageChangedForUid.setDeliveryGroupKey("package", String.valueOf(TEST_UID));
+ optionsPackageChangedForUid.setDeliveryGroupMatchingKey("package",
+ String.valueOf(TEST_UID));
optionsPackageChangedForUid.setDeliveryGroupExtrasMerger(extrasMerger);
final Intent secondPackageChangedForUid = createPackageChangedIntent(TEST_UID,
@@ -662,7 +663,8 @@ public class BroadcastQueueModernImplTest {
final BroadcastOptions optionsPackageChangedForUid2 = BroadcastOptions.makeBasic();
optionsPackageChangedForUid.setDeliveryGroupPolicy(
BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED);
- optionsPackageChangedForUid.setDeliveryGroupKey("package", String.valueOf(TEST_UID2));
+ optionsPackageChangedForUid.setDeliveryGroupMatchingKey("package",
+ String.valueOf(TEST_UID2));
optionsPackageChangedForUid.setDeliveryGroupExtrasMerger(extrasMerger);
// Halt all processing so that we get a consistent view
@@ -685,6 +687,75 @@ public class BroadcastQueueModernImplTest {
verifyPendingRecords(queue, List.of(packageChangedForUid2, expectedPackageChangedForUid));
}
+ @Test
+ public void testDeliveryGroupPolicy_matchingFilter() {
+ final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
+ final BroadcastOptions optionsTimeTick = BroadcastOptions.makeBasic();
+ optionsTimeTick.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+
+ final Intent musicVolumeChanged = new Intent(AudioManager.VOLUME_CHANGED_ACTION);
+ musicVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+ AudioManager.STREAM_MUSIC);
+ final IntentFilter filterMusicVolumeChanged = new IntentFilter(
+ AudioManager.VOLUME_CHANGED_ACTION);
+ filterMusicVolumeChanged.addExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+ AudioManager.STREAM_MUSIC);
+ final BroadcastOptions optionsMusicVolumeChanged = BroadcastOptions.makeBasic();
+ optionsMusicVolumeChanged.setDeliveryGroupPolicy(
+ BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+ optionsMusicVolumeChanged.setDeliveryGroupMatchingFilter(filterMusicVolumeChanged);
+
+ final Intent alarmVolumeChanged = new Intent(AudioManager.VOLUME_CHANGED_ACTION);
+ alarmVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+ AudioManager.STREAM_ALARM);
+ final IntentFilter filterAlarmVolumeChanged = new IntentFilter(
+ AudioManager.VOLUME_CHANGED_ACTION);
+ filterAlarmVolumeChanged.addExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+ AudioManager.STREAM_ALARM);
+ final BroadcastOptions optionsAlarmVolumeChanged = BroadcastOptions.makeBasic();
+ optionsAlarmVolumeChanged.setDeliveryGroupPolicy(
+ BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+ optionsAlarmVolumeChanged.setDeliveryGroupMatchingFilter(filterAlarmVolumeChanged);
+
+ // Halt all processing so that we get a consistent view
+ mHandlerThread.getLooper().getQueue().postSyncBarrier();
+
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged,
+ optionsMusicVolumeChanged));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(alarmVolumeChanged,
+ optionsAlarmVolumeChanged));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged,
+ optionsMusicVolumeChanged));
+
+ final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+ // Verify that the older musicVolumeChanged has been removed.
+ verifyPendingRecords(queue,
+ List.of(timeTick, alarmVolumeChanged, musicVolumeChanged));
+
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(alarmVolumeChanged,
+ optionsAlarmVolumeChanged));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged,
+ optionsMusicVolumeChanged));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(alarmVolumeChanged,
+ optionsAlarmVolumeChanged));
+ // Verify that the older alarmVolumeChanged has been removed.
+ verifyPendingRecords(queue,
+ List.of(timeTick, musicVolumeChanged, alarmVolumeChanged));
+
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged,
+ optionsMusicVolumeChanged));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(alarmVolumeChanged,
+ optionsAlarmVolumeChanged));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick));
+ // Verify that the older timeTick has been removed.
+ verifyPendingRecords(queue,
+ List.of(musicVolumeChanged, alarmVolumeChanged, timeTick));
+ }
+
private Intent createPackageChangedIntent(int uid, List<String> componentNameList) {
final Intent packageChangedIntent = new Intent(Intent.ACTION_PACKAGE_CHANGED);
packageChangedIntent.putExtra(Intent.EXTRA_UID, uid);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index 05ed0e236b4d..3864c88d71e4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -36,13 +36,17 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import android.app.ActivityManagerInternal;
+import android.app.BroadcastOptions;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
+import android.os.PersistableBundle;
import android.os.Process;
import android.os.UserHandle;
+import android.telephony.SubscriptionManager;
import android.util.SparseArray;
import androidx.test.filters.SmallTest;
@@ -176,9 +180,8 @@ public class BroadcastRecordTest {
intent.putExtra(Intent.EXTRA_INDEX, 42);
final BroadcastRecord r = createBroadcastRecord(
List.of(createResolveInfo(PACKAGE1, getAppId(1))), UserHandle.USER_ALL, intent,
- (uid, extras) -> {
- return Bundle.EMPTY;
- });
+ (uid, extras) -> Bundle.EMPTY,
+ null /* options */);
final Intent actual = r.getReceiverIntent(r.receivers.get(0));
assertEquals(PACKAGE1, actual.getComponent().getPackageName());
assertEquals(-1, actual.getIntExtra(Intent.EXTRA_INDEX, -1));
@@ -192,9 +195,8 @@ public class BroadcastRecordTest {
intent.putExtra(Intent.EXTRA_INDEX, 42);
final BroadcastRecord r = createBroadcastRecord(
List.of(createResolveInfo(PACKAGE1, getAppId(1))), UserHandle.USER_ALL, intent,
- (uid, extras) -> {
- return null;
- });
+ (uid, extras) -> null,
+ null /* options */);
final Intent actual = r.getReceiverIntent(r.receivers.get(0));
assertNull(actual);
assertNull(r.intent.getComponent());
@@ -290,6 +292,122 @@ public class BroadcastRecordTest {
assertEquals(origReceiversSize, br.receivers.size());
}
+ @Test
+ public void testMatchesDeliveryGroup() {
+ final List<ResolveInfo> receivers = List.of(createResolveInfo(PACKAGE1, getAppId(1)));
+
+ final Intent intent1 = new Intent(Intent.ACTION_SERVICE_STATE);
+ intent1.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 0);
+ intent1.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 1);
+ final BroadcastOptions options1 = BroadcastOptions.makeBasic();
+ final BroadcastRecord record1 = createBroadcastRecord(receivers, UserHandle.USER_ALL,
+ intent1, options1);
+
+ final Intent intent2 = new Intent(Intent.ACTION_SERVICE_STATE);
+ intent2.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 0);
+ intent2.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 2);
+ final BroadcastOptions options2 = BroadcastOptions.makeBasic();
+ final BroadcastRecord record2 = createBroadcastRecord(receivers, UserHandle.USER_ALL,
+ intent2, options2);
+
+ assertTrue(record2.matchesDeliveryGroup(record1));
+ }
+
+ @Test
+ public void testMatchesDeliveryGroup_withMatchingKey() {
+ final List<ResolveInfo> receivers = List.of(createResolveInfo(PACKAGE1, getAppId(1)));
+
+ final Intent intent1 = new Intent(Intent.ACTION_SERVICE_STATE);
+ intent1.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 0);
+ intent1.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 1);
+ final BroadcastOptions options1 = BroadcastOptions.makeBasic();
+ options1.setDeliveryGroupMatchingKey(Intent.ACTION_SERVICE_STATE, "key1");
+ final BroadcastRecord record1 = createBroadcastRecord(receivers, UserHandle.USER_ALL,
+ intent1, options1);
+
+ final Intent intent2 = new Intent(Intent.ACTION_SERVICE_STATE);
+ intent2.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 0);
+ intent2.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 2);
+ final BroadcastOptions options2 = BroadcastOptions.makeBasic();
+ options2.setDeliveryGroupMatchingKey(Intent.ACTION_SERVICE_STATE, "key2");
+ final BroadcastRecord record2 = createBroadcastRecord(receivers, UserHandle.USER_ALL,
+ intent2, options2);
+
+ final Intent intent3 = new Intent(Intent.ACTION_SERVICE_STATE);
+ intent3.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 1);
+ intent3.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 3);
+ final BroadcastOptions options3 = BroadcastOptions.makeBasic();
+ options3.setDeliveryGroupMatchingKey(Intent.ACTION_SERVICE_STATE, "key1");
+ final BroadcastRecord record3 = createBroadcastRecord(receivers, UserHandle.USER_ALL,
+ intent3, options3);
+
+ // record2 and record1 have different matching keys, so their delivery groups
+ // shouldn't match
+ assertFalse(record2.matchesDeliveryGroup(record1));
+ // record3 and record2 have different matching keys, so their delivery groups
+ // shouldn't match
+ assertFalse(record3.matchesDeliveryGroup(record2));
+ // record3 and record1 have same matching keys, so their delivery groups should match even
+ // if the intent has different extras.
+ assertTrue(record3.matchesDeliveryGroup(record1));
+ }
+
+ @Test
+ public void testMatchesDeliveryGroup_withMatchingFilter() {
+ final List<ResolveInfo> receivers = List.of(createResolveInfo(PACKAGE1, getAppId(1)));
+
+ final Intent intent1 = new Intent(Intent.ACTION_SERVICE_STATE);
+ intent1.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 0);
+ intent1.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 1);
+ intent1.putExtra(Intent.EXTRA_REASON, "reason1");
+ final IntentFilter filter1 = new IntentFilter(Intent.ACTION_SERVICE_STATE);
+ final PersistableBundle bundle1 = new PersistableBundle();
+ bundle1.putInt(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 0);
+ bundle1.putInt(SubscriptionManager.EXTRA_SLOT_INDEX, 1);
+ filter1.setExtras(bundle1);
+ final BroadcastOptions options1 = BroadcastOptions.makeBasic();
+ options1.setDeliveryGroupMatchingFilter(filter1);
+ final BroadcastRecord record1 = createBroadcastRecord(receivers, UserHandle.USER_ALL,
+ intent1, options1);
+
+ final Intent intent2 = new Intent(Intent.ACTION_SERVICE_STATE);
+ intent2.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 0);
+ intent2.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 2);
+ intent2.putExtra(Intent.EXTRA_REASON, "reason2");
+ final IntentFilter filter2 = new IntentFilter(Intent.ACTION_SERVICE_STATE);
+ final PersistableBundle bundle2 = new PersistableBundle();
+ bundle2.putInt(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 0);
+ bundle2.putInt(SubscriptionManager.EXTRA_SLOT_INDEX, 2);
+ filter2.setExtras(bundle2);
+ final BroadcastOptions options2 = BroadcastOptions.makeBasic();
+ options2.setDeliveryGroupMatchingFilter(filter2);
+ final BroadcastRecord record2 = createBroadcastRecord(receivers, UserHandle.USER_ALL,
+ intent2, options2);
+
+ final Intent intent3 = new Intent(Intent.ACTION_SERVICE_STATE);
+ intent3.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 1);
+ intent3.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 3);
+ intent3.putExtra(Intent.EXTRA_REASON, "reason3");
+ final IntentFilter filter3 = new IntentFilter(Intent.ACTION_SERVICE_STATE);
+ final PersistableBundle bundle3 = new PersistableBundle();
+ bundle3.putInt(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 0);
+ bundle3.putInt(SubscriptionManager.EXTRA_SLOT_INDEX, 1);
+ filter3.setExtras(bundle3);
+ final BroadcastOptions options3 = BroadcastOptions.makeBasic();
+ options3.setDeliveryGroupMatchingFilter(filter3);
+ final BroadcastRecord record3 = createBroadcastRecord(receivers, UserHandle.USER_ALL,
+ intent3, options3);
+
+ // record2's matchingFilter doesn't match record1's intent, so their delivery groups
+ // shouldn't match
+ assertFalse(record2.matchesDeliveryGroup(record1));
+ // record3's matchingFilter doesn't match record2's intent, so their delivery groups
+ // shouldn't match
+ assertFalse(record3.matchesDeliveryGroup(record2));
+ // record3's matchingFilter matches record1's intent, so their delivery groups should match.
+ assertTrue(record3.matchesDeliveryGroup(record1));
+ }
+
private BroadcastRecord createBootCompletedBroadcastRecord(String action) {
final List<ResolveInfo> receivers = createReceiverInfos(PACKAGE_LIST, USER_LIST);
final BroadcastRecord br = createBroadcastRecord(receivers, UserHandle.USER_ALL,
@@ -539,11 +657,19 @@ public class BroadcastRecordTest {
private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
Intent intent) {
- return createBroadcastRecord(receivers, userId, intent, null);
+ return createBroadcastRecord(receivers, userId, intent, null /* filterExtrasForReceiver */,
+ null /* options */);
+ }
+
+ private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
+ Intent intent, BroadcastOptions options) {
+ return createBroadcastRecord(receivers, userId, intent, null /* filterExtrasForReceiver */,
+ options);
}
private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
- Intent intent, BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
+ Intent intent, BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+ BroadcastOptions options) {
return new BroadcastRecord(
mQueue /* queue */,
intent,
@@ -558,7 +684,7 @@ public class BroadcastRecordTest {
null /* excludedPermissions */,
null /* excludedPackages */,
0 /* appOp */,
- null /* options */,
+ options,
new ArrayList<>(receivers), // Make a copy to not affect the original list.
null /* resultToApp */,
null /* resultTo */,