diff options
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 */, |