Merge "Allow senders to specify delivery group policies."
This commit is contained in:
committed by
Android (Google) Code Review
commit
56cec3db5f
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user