Allow senders to specify "merged" delivery group policy.
Bug: 249160234 Test: atest FrameworksMockingServicesTests:BroadcastQueueTest Test: atest FrameworksMockingServicesTests:BroadcastQueueModernImplTest Change-Id: Ic998c98e85b17e8d6958178904d901d4c6fd553b
This commit is contained in:
@@ -31,6 +31,7 @@ import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.BundleMerger;
|
||||
import android.os.PowerExemptionManager;
|
||||
import android.os.PowerExemptionManager.ReasonCode;
|
||||
import android.os.PowerExemptionManager.TempAllowListType;
|
||||
@@ -67,6 +68,7 @@ public class BroadcastOptions extends ComponentOptions {
|
||||
private @Nullable IntentFilter mRemoveMatchingFilter;
|
||||
private @DeliveryGroupPolicy int mDeliveryGroupPolicy;
|
||||
private @Nullable String mDeliveryGroupKey;
|
||||
private @Nullable BundleMerger mDeliveryGroupExtrasMerger;
|
||||
|
||||
/**
|
||||
* Change ID which is invalid.
|
||||
@@ -217,6 +219,12 @@ public class BroadcastOptions extends ComponentOptions {
|
||||
private static final String KEY_DELIVERY_GROUP_KEY =
|
||||
"android:broadcast.deliveryGroupKey";
|
||||
|
||||
/**
|
||||
* Corresponds to {@link #setDeliveryGroupExtrasMerger(BundleMerger)}.
|
||||
*/
|
||||
private static final String KEY_DELIVERY_GROUP_EXTRAS_MERGER =
|
||||
"android:broadcast.deliveryGroupExtrasMerger";
|
||||
|
||||
/**
|
||||
* The list of delivery group policies which specify how multiple broadcasts belonging to
|
||||
* the same delivery group has to be handled.
|
||||
@@ -225,6 +233,7 @@ public class BroadcastOptions extends ComponentOptions {
|
||||
@IntDef(flag = true, prefix = { "DELIVERY_GROUP_POLICY_" }, value = {
|
||||
DELIVERY_GROUP_POLICY_ALL,
|
||||
DELIVERY_GROUP_POLICY_MOST_RECENT,
|
||||
DELIVERY_GROUP_POLICY_MERGED,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface DeliveryGroupPolicy {}
|
||||
@@ -245,6 +254,14 @@ public class BroadcastOptions extends ComponentOptions {
|
||||
*/
|
||||
public static final int DELIVERY_GROUP_POLICY_MOST_RECENT = 1;
|
||||
|
||||
/**
|
||||
* Delivery group policy that indicates that the extras data from the broadcasts in the
|
||||
* delivery group need to be merged into a single broadcast and the rest can be dropped.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int DELIVERY_GROUP_POLICY_MERGED = 2;
|
||||
|
||||
public static BroadcastOptions makeBasic() {
|
||||
BroadcastOptions opts = new BroadcastOptions();
|
||||
return opts;
|
||||
@@ -295,6 +312,8 @@ public class BroadcastOptions extends ComponentOptions {
|
||||
mDeliveryGroupPolicy = opts.getInt(KEY_DELIVERY_GROUP_POLICY,
|
||||
DELIVERY_GROUP_POLICY_ALL);
|
||||
mDeliveryGroupKey = opts.getString(KEY_DELIVERY_GROUP_KEY);
|
||||
mDeliveryGroupExtrasMerger = opts.getParcelable(KEY_DELIVERY_GROUP_EXTRAS_MERGER,
|
||||
BundleMerger.class);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -753,6 +772,25 @@ public class BroadcastOptions extends ComponentOptions {
|
||||
return mDeliveryGroupKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link BundleMerger} that specifies how to merge the extras data from
|
||||
* broadcasts in a delivery group.
|
||||
*
|
||||
* <p>Note that this value will be ignored if the delivery group policy is not set as
|
||||
* {@link #DELIVERY_GROUP_POLICY_MERGED}.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public void setDeliveryGroupExtrasMerger(@NonNull BundleMerger extrasMerger) {
|
||||
Preconditions.checkNotNull(extrasMerger);
|
||||
mDeliveryGroupExtrasMerger = extrasMerger;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public @Nullable BundleMerger getDeliveryGroupExtrasMerger() {
|
||||
return mDeliveryGroupExtrasMerger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the created options as a Bundle, which can be passed to
|
||||
* {@link android.content.Context#sendBroadcast(android.content.Intent)
|
||||
@@ -760,6 +798,10 @@ public class BroadcastOptions extends ComponentOptions {
|
||||
* Note that the returned Bundle is still owned by the BroadcastOptions
|
||||
* object; you must not modify it, but can supply it to the sendBroadcast
|
||||
* methods that take an options Bundle.
|
||||
*
|
||||
* @throws IllegalStateException if the broadcast option values are inconsistent. For example,
|
||||
* if the delivery group policy is specified as "MERGED" but no
|
||||
* extras merger is supplied.
|
||||
*/
|
||||
@Override
|
||||
public Bundle toBundle() {
|
||||
@@ -810,6 +852,15 @@ public class BroadcastOptions extends ComponentOptions {
|
||||
if (mDeliveryGroupKey != null) {
|
||||
b.putString(KEY_DELIVERY_GROUP_KEY, mDeliveryGroupKey);
|
||||
}
|
||||
if (mDeliveryGroupPolicy == DELIVERY_GROUP_POLICY_MERGED) {
|
||||
if (mDeliveryGroupExtrasMerger != null) {
|
||||
b.putParcelable(KEY_DELIVERY_GROUP_EXTRAS_MERGER,
|
||||
mDeliveryGroupExtrasMerger);
|
||||
} else {
|
||||
throw new IllegalStateException("Extras merger cannot be empty "
|
||||
+ "when delivery group policy is 'MERGED'");
|
||||
}
|
||||
}
|
||||
return b.isEmpty() ? null : b;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.BundleMerger;
|
||||
import android.os.IBinder;
|
||||
import android.os.IncidentManager;
|
||||
import android.os.Parcel;
|
||||
@@ -11071,6 +11072,20 @@ public class Intent implements Parcelable, Cloneable {
|
||||
return changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the extras data in this intent with that of other supplied intent using the
|
||||
* strategy specified using {@code extrasMerger}.
|
||||
*
|
||||
* <p> Note the extras data in this intent is treated as the {@code first} param
|
||||
* and the extras data in {@code other} intent is treated as the {@code last} param
|
||||
* when using the passed in {@link BundleMerger} object.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public void mergeExtras(@NonNull Intent other, @NonNull BundleMerger extrasMerger) {
|
||||
mExtras = extrasMerger.merge(mExtras, other.mExtras);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper class holding an Intent and implementing comparisons on it for
|
||||
* the purpose of filtering. The class implements its
|
||||
|
||||
@@ -58,6 +58,7 @@ import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.os.Bundle;
|
||||
import android.os.BundleMerger;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.Process;
|
||||
@@ -543,16 +544,7 @@ 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);
|
||||
}
|
||||
applyDeliveryGroupPolicy(r);
|
||||
|
||||
if (r.isReplacePending()) {
|
||||
// Leave the skipped broadcasts intact in queue, so that we can
|
||||
@@ -609,6 +601,41 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
}
|
||||
}
|
||||
|
||||
private void applyDeliveryGroupPolicy(@NonNull BroadcastRecord r) {
|
||||
final int policy = (r.options != null)
|
||||
? r.options.getDeliveryGroupPolicy() : BroadcastOptions.DELIVERY_GROUP_POLICY_ALL;
|
||||
final BroadcastConsumer broadcastConsumer;
|
||||
switch (policy) {
|
||||
case BroadcastOptions.DELIVERY_GROUP_POLICY_ALL:
|
||||
// Older broadcasts need to be left as is in this case, so nothing more to do.
|
||||
return;
|
||||
case BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT:
|
||||
broadcastConsumer = mBroadcastConsumerSkipAndCanceled;
|
||||
break;
|
||||
case BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED:
|
||||
final BundleMerger extrasMerger = r.options.getDeliveryGroupExtrasMerger();
|
||||
if (extrasMerger == null) {
|
||||
// Extras merger is required to be able to merge the extras. So, if it's not
|
||||
// supplied, then ignore the delivery group policy.
|
||||
return;
|
||||
}
|
||||
broadcastConsumer = (record, recordIndex) -> {
|
||||
r.intent.mergeExtras(record.intent, extrasMerger);
|
||||
mBroadcastConsumerSkipAndCanceled.accept(record, recordIndex);
|
||||
};
|
||||
break;
|
||||
default:
|
||||
logw("Unknown delivery group policy: " + policy);
|
||||
return;
|
||||
}
|
||||
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);
|
||||
}, broadcastConsumer, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule the currently active broadcast on the given queue when we know
|
||||
* the process is cold. This kicks off a cold start and will eventually call
|
||||
|
||||
@@ -33,6 +33,7 @@ import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
@@ -43,6 +44,7 @@ import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.BundleMerger;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
@@ -57,12 +59,15 @@ import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class BroadcastQueueModernImplTest {
|
||||
private static final int TEST_UID = android.os.Process.FIRST_APPLICATION_UID;
|
||||
private static final int TEST_UID2 = android.os.Process.FIRST_APPLICATION_UID + 1;
|
||||
|
||||
@Mock ActivityManagerService mAms;
|
||||
@Mock ProcessRecord mProcess;
|
||||
@@ -467,6 +472,62 @@ public class BroadcastQueueModernImplTest {
|
||||
List.of(musicVolumeChanged, alarmVolumeChanged, timeTick));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that sending a broadcast with DELIVERY_GROUP_POLICY_MERGED works as expected.
|
||||
*/
|
||||
@Test
|
||||
public void testDeliveryGroupPolicy_merged() {
|
||||
final BundleMerger extrasMerger = new BundleMerger();
|
||||
extrasMerger.setMergeStrategy(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST,
|
||||
BundleMerger.STRATEGY_ARRAY_APPEND);
|
||||
|
||||
final Intent packageChangedForUid = createPackageChangedIntent(TEST_UID,
|
||||
List.of("com.testuid.component1"));
|
||||
final BroadcastOptions optionsPackageChangedForUid = BroadcastOptions.makeBasic();
|
||||
optionsPackageChangedForUid.setDeliveryGroupPolicy(
|
||||
BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED);
|
||||
optionsPackageChangedForUid.setDeliveryGroupKey("package", String.valueOf(TEST_UID));
|
||||
optionsPackageChangedForUid.setDeliveryGroupExtrasMerger(extrasMerger);
|
||||
|
||||
final Intent secondPackageChangedForUid = createPackageChangedIntent(TEST_UID,
|
||||
List.of("com.testuid.component2", "com.testuid.component3"));
|
||||
|
||||
final Intent packageChangedForUid2 = createPackageChangedIntent(TEST_UID2,
|
||||
List.of("com.testuid2.component1"));
|
||||
final BroadcastOptions optionsPackageChangedForUid2 = BroadcastOptions.makeBasic();
|
||||
optionsPackageChangedForUid.setDeliveryGroupPolicy(
|
||||
BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED);
|
||||
optionsPackageChangedForUid.setDeliveryGroupKey("package", String.valueOf(TEST_UID2));
|
||||
optionsPackageChangedForUid.setDeliveryGroupExtrasMerger(extrasMerger);
|
||||
|
||||
// Halt all processing so that we get a consistent view
|
||||
mHandlerThread.getLooper().getQueue().postSyncBarrier();
|
||||
|
||||
mImpl.enqueueBroadcastLocked(makeBroadcastRecord(packageChangedForUid,
|
||||
optionsPackageChangedForUid));
|
||||
mImpl.enqueueBroadcastLocked(makeBroadcastRecord(packageChangedForUid2,
|
||||
optionsPackageChangedForUid2));
|
||||
mImpl.enqueueBroadcastLocked(makeBroadcastRecord(secondPackageChangedForUid,
|
||||
optionsPackageChangedForUid));
|
||||
|
||||
final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN,
|
||||
getUidForPackage(PACKAGE_GREEN));
|
||||
final Intent expectedPackageChangedForUid = createPackageChangedIntent(TEST_UID,
|
||||
List.of("com.testuid.component2", "com.testuid.component3",
|
||||
"com.testuid.component1"));
|
||||
// Verify that packageChangedForUid and secondPackageChangedForUid broadcasts
|
||||
// have been merged.
|
||||
verifyPendingRecords(queue, List.of(packageChangedForUid2, expectedPackageChangedForUid));
|
||||
}
|
||||
|
||||
private Intent createPackageChangedIntent(int uid, List<String> componentNameList) {
|
||||
final Intent packageChangedIntent = new Intent(Intent.ACTION_PACKAGE_CHANGED);
|
||||
packageChangedIntent.putExtra(Intent.EXTRA_UID, uid);
|
||||
packageChangedIntent.putExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST,
|
||||
componentNameList.toArray());
|
||||
return packageChangedIntent;
|
||||
}
|
||||
|
||||
private void verifyPendingRecords(BroadcastProcessQueue queue,
|
||||
List<Intent> intents) {
|
||||
for (int i = 0; i < intents.size(); i++) {
|
||||
@@ -477,9 +538,45 @@ public class BroadcastQueueModernImplTest {
|
||||
+ ", actual_extras=" + actualIntent.getExtras()
|
||||
+ ", expected_extras=" + expectedIntent.getExtras();
|
||||
assertTrue(errMsg, actualIntent.filterEquals(expectedIntent));
|
||||
assertTrue(errMsg, Bundle.kindofEquals(
|
||||
actualIntent.getExtras(), expectedIntent.getExtras()));
|
||||
assertBundleEquals(expectedIntent.getExtras(), actualIntent.getExtras());
|
||||
}
|
||||
assertTrue(queue.isEmpty());
|
||||
}
|
||||
|
||||
private void assertBundleEquals(Bundle expected, Bundle actual) {
|
||||
final String errMsg = "expected=" + expected + ", actual=" + actual;
|
||||
if (expected == actual) {
|
||||
return;
|
||||
} else if (expected == null || actual == null) {
|
||||
fail(errMsg);
|
||||
}
|
||||
if (!expected.keySet().equals(actual.keySet())) {
|
||||
fail(errMsg);
|
||||
}
|
||||
for (String key : expected.keySet()) {
|
||||
final Object expectedValue = expected.get(key);
|
||||
final Object actualValue = actual.get(key);
|
||||
if (expectedValue == actualValue) {
|
||||
continue;
|
||||
} else if (expectedValue == null || actualValue == null) {
|
||||
fail(errMsg);
|
||||
}
|
||||
assertEquals(errMsg, expectedValue.getClass(), actualValue.getClass());
|
||||
if (expectedValue.getClass().isArray()) {
|
||||
assertEquals(errMsg, Array.getLength(expectedValue), Array.getLength(actualValue));
|
||||
for (int i = 0; i < Array.getLength(expectedValue); ++i) {
|
||||
assertEquals(errMsg, Array.get(expectedValue, i), Array.get(actualValue, i));
|
||||
}
|
||||
} else if (expectedValue instanceof ArrayList) {
|
||||
final ArrayList<?> expectedList = (ArrayList<?>) expectedValue;
|
||||
final ArrayList<?> actualList = (ArrayList<?>) actualValue;
|
||||
assertEquals(errMsg, expectedList.size(), actualList.size());
|
||||
for (int i = 0; i < expectedList.size(); ++i) {
|
||||
assertEquals(errMsg, expectedList.get(i), actualList.get(i));
|
||||
}
|
||||
} else {
|
||||
assertEquals(errMsg, expectedValue, actualValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user