Merge "Allow senders to specify delivery group policies."

This commit is contained in:
Sudheer Shanka
2022-10-19 18:22:20 +00:00
committed by Android (Google) Code Review
4 changed files with 197 additions and 0 deletions

View File

@@ -16,6 +16,7 @@
package android.app;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -34,6 +35,10 @@ import android.os.PowerExemptionManager;
import android.os.PowerExemptionManager.ReasonCode;
import android.os.PowerExemptionManager.TempAllowListType;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/**
@@ -59,6 +64,8 @@ public class BroadcastOptions extends ComponentOptions {
private boolean mIsAlarmBroadcast = false;
private long mIdForResponseEvent;
private @Nullable IntentFilter mRemoveMatchingFilter;
private @DeliveryGroupPolicy int mDeliveryGroupPolicy;
private @Nullable String mDeliveryGroupKey;
/**
* Change ID which is invalid.
@@ -190,6 +197,46 @@ public class BroadcastOptions extends ComponentOptions {
private static final String KEY_REMOVE_MATCHING_FILTER =
"android:broadcast.removeMatchingFilter";
/**
* Corresponds to {@link #setDeliveryGroupPolicy(int)}.
*/
private static final String KEY_DELIVERY_GROUP_POLICY =
"android:broadcast.deliveryGroupPolicy";
/**
* Corresponds to {@link #setDeliveryGroupKey(String, String)}.
*/
private static final String KEY_DELIVERY_GROUP_KEY =
"android:broadcast.deliveryGroupKey";
/**
* The list of delivery group policies which specify how multiple broadcasts belonging to
* the same delivery group has to be handled.
* @hide
*/
@IntDef(flag = true, prefix = { "DELIVERY_GROUP_POLICY_" }, value = {
DELIVERY_GROUP_POLICY_ALL,
DELIVERY_GROUP_POLICY_MOST_RECENT,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DeliveryGroupPolicy {}
/**
* Delivery group policy that indicates that all the broadcasts in the delivery group
* need to be delivered as is.
*
* @hide
*/
public static final int DELIVERY_GROUP_POLICY_ALL = 0;
/**
* Delivery group policy that indicates that only the most recent broadcast in the delivery
* group need to be delivered and the rest can be dropped.
*
* @hide
*/
public static final int DELIVERY_GROUP_POLICY_MOST_RECENT = 1;
public static BroadcastOptions makeBasic() {
BroadcastOptions opts = new BroadcastOptions();
return opts;
@@ -236,6 +283,9 @@ public class BroadcastOptions extends ComponentOptions {
mIsAlarmBroadcast = opts.getBoolean(KEY_ALARM_BROADCAST, false);
mRemoveMatchingFilter = opts.getParcelable(KEY_REMOVE_MATCHING_FILTER,
IntentFilter.class);
mDeliveryGroupPolicy = opts.getInt(KEY_DELIVERY_GROUP_POLICY,
DELIVERY_GROUP_POLICY_ALL);
mDeliveryGroupKey = opts.getString(KEY_DELIVERY_GROUP_KEY);
}
/**
@@ -638,6 +688,41 @@ public class BroadcastOptions extends ComponentOptions {
return mRemoveMatchingFilter;
}
/**
* Set delivery group policy for this broadcast to specify how multiple broadcasts belonging to
* the same delivery group has to be handled.
*
* @hide
*/
public void setDeliveryGroupPolicy(@DeliveryGroupPolicy int policy) {
mDeliveryGroupPolicy = policy;
}
/** @hide */
public @DeliveryGroupPolicy int getDeliveryGroupPolicy() {
return mDeliveryGroupPolicy;
}
/**
* 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.
*
* @hide
*/
public void setDeliveryGroupKey(@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;
}
/** @hide */
public String getDeliveryGroupKey() {
return mDeliveryGroupKey;
}
/**
* Returns the created options as a Bundle, which can be passed to
* {@link android.content.Context#sendBroadcast(android.content.Intent)
@@ -686,6 +771,12 @@ public class BroadcastOptions extends ComponentOptions {
if (mRemoveMatchingFilter != null) {
b.putParcelable(KEY_REMOVE_MATCHING_FILTER, mRemoveMatchingFilter);
}
if (mDeliveryGroupPolicy != DELIVERY_GROUP_POLICY_ALL) {
b.putInt(KEY_DELIVERY_GROUP_POLICY, mDeliveryGroupPolicy);
}
if (mDeliveryGroupKey != null) {
b.putString(KEY_DELIVERY_GROUP_KEY, mDeliveryGroupKey);
}
return b.isEmpty() ? null : b;
}
}

View File

@@ -44,6 +44,7 @@ import android.annotation.Nullable;
import android.annotation.UptimeMillisLong;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.BroadcastOptions;
import android.app.IApplicationThread;
import android.app.RemoteServiceException.CannotDeliverBroadcastException;
import android.app.UidObserver;
@@ -534,6 +535,17 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}, mBroadcastConsumerSkipAndCanceled, true);
}
final int policy = (r.options != null)
? r.options.getDeliveryGroupPolicy() : BroadcastOptions.DELIVERY_GROUP_POLICY_ALL;
if (policy == BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT) {
forEachMatchingBroadcast(QUEUE_PREDICATE_ANY, (testRecord, testIndex) -> {
// We only allow caller to remove broadcasts they enqueued
return (r.callingUid == testRecord.callingUid)
&& (r.userId == testRecord.userId)
&& r.matchesDeliveryGroup(testRecord);
}, mBroadcastConsumerSkipAndCanceled, true);
}
if (r.isReplacePending()) {
// Leave the skipped broadcasts intact in queue, so that we can
// replace them at their current position during enqueue below

View File

@@ -796,6 +796,16 @@ 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);
}
return Objects.equals(key, otherKey);
}
@Override
public String toString() {
if (mCachedToString == null) {

View File

@@ -39,6 +39,8 @@ import android.app.AppOpsManager;
import android.app.BroadcastOptions;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.os.Bundle;
import android.os.HandlerThread;
import android.os.UserHandle;
import android.provider.Settings;
@@ -386,4 +388,86 @@ public class BroadcastQueueModernImplTest {
assertEquals(Intent.ACTION_SCREEN_OFF, queue.getActive().intent.getAction());
assertTrue(queue.isEmpty());
}
/**
* Verify that sending a broadcast with DELIVERY_GROUP_POLICY_MOST_RECENT works as expected.
*/
@Test
public void testDeliveryGroupPolicy_mostRecent() {
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 BroadcastOptions optionsMusicVolumeChanged = BroadcastOptions.makeBasic();
optionsMusicVolumeChanged.setDeliveryGroupPolicy(
BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
optionsMusicVolumeChanged.setDeliveryGroupKey("audio",
String.valueOf(AudioManager.STREAM_MUSIC));
final Intent alarmVolumeChanged = new Intent(AudioManager.VOLUME_CHANGED_ACTION);
alarmVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
AudioManager.STREAM_ALARM);
final BroadcastOptions optionsAlarmVolumeChanged = BroadcastOptions.makeBasic();
optionsAlarmVolumeChanged.setDeliveryGroupPolicy(
BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
optionsAlarmVolumeChanged.setDeliveryGroupKey("audio",
String.valueOf(AudioManager.STREAM_ALARM));
// 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 void verifyPendingRecords(BroadcastProcessQueue queue,
List<Intent> intents) {
for (int i = 0; i < intents.size(); i++) {
queue.makeActiveNextPending();
final Intent actualIntent = queue.getActive().intent;
final Intent expectedIntent = intents.get(i);
final String errMsg = "actual=" + actualIntent + ", expected=" + expectedIntent
+ ", actual_extras=" + actualIntent.getExtras()
+ ", expected_extras=" + expectedIntent.getExtras();
assertTrue(errMsg, actualIntent.filterEquals(expectedIntent));
assertTrue(errMsg, Bundle.kindofEquals(
actualIntent.getExtras(), expectedIntent.getExtras()));
}
assertTrue(queue.isEmpty());
}
}