summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Sudheer Shanka <sudheersai@google.com> 2022-10-13 18:31:27 +0000
committer Sudheer Shanka <sudheersai@google.com> 2022-10-19 12:33:39 +0000
commitd1712e459706eb78744222baf8e80933e0a56cf3 (patch)
treed7855cf2a34c56dda37accc9cf8716222593b5a0
parent9cd45bac5971a29a47a4dbf6a72a857c22f002f3 (diff)
Allow senders to specify delivery group policies.
While sending the broadcast, sender can specify what delivery group the broadcast belongs to and how it should be treated if there are already broadcasts in the same delivery group waiting to be delivered. Bug: 249160234 Test: atest FrameworksMockingServicesTests:BroadcastQueueTest Test: atest FrameworksMockingServicesTests:BroadcastQueueModernImplTest Change-Id: Ie6e8164ff37ec5501482adc9419959cc7e149d90
-rw-r--r--core/java/android/app/BroadcastOptions.java91
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueModernImpl.java12
-rw-r--r--services/core/java/com/android/server/am/BroadcastRecord.java10
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java84
4 files changed, 197 insertions, 0 deletions
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index c2df8022af0f..13da1901e559 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -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);
}
/**
@@ -639,6 +689,41 @@ public class BroadcastOptions extends ComponentOptions {
}
/**
+ * 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)
* Context.sendBroadcast(Intent)} and related methods.
@@ -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;
}
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 89a0283a4264..c6cd622855a6 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -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;
@@ -526,6 +527,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
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index bcc76e949e5f..e9995c329997 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -792,6 +792,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) {
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 86915da75467..9d2a33584a53 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -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());
+ }
}