Merge "Allow senders to specify the delivery group via matching filters."

This commit is contained in:
Sudheer Shanka
2022-11-22 12:01:06 +00:00
committed by Android (Google) Code Review
7 changed files with 852 additions and 39 deletions

View File

@@ -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);
}

View File

@@ -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)}.
@@ -217,6 +218,12 @@ public class BroadcastOptions extends ComponentOptions {
private static final String KEY_DELIVERY_GROUP_EXTRAS_MERGER =
"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.
@@ -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,15 +852,32 @@ 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)
@@ -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;
}
}

View File

@@ -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;
@@ -1767,6 +1785,423 @@ public class IntentFilter implements Parcelable {
return null;
}
/**
* 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>.
@@ -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() {

View File

@@ -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 {

View File

@@ -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);
}
return Objects.equals(key, otherKey);
if (newMatchingFilter != null && !newMatchingFilter.asPredicate().test(oldRecord.intent)) {
return false;
}
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

View File

@@ -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);

View File

@@ -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, BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
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,
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 */,