Loading core/java/android/app/BroadcastOptions.java +51 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. Loading Loading @@ -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. Loading @@ -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 {} Loading @@ -247,6 +256,14 @@ public class BroadcastOptions extends ComponentOptions { @SystemApi 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; Loading Loading @@ -297,6 +314,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); } /** Loading Loading @@ -774,6 +793,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) Loading @@ -781,6 +819,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() { Loading Loading @@ -831,6 +873,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; } } core/java/android/content/Intent.java +15 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +37 −10 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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 Loading services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java +99 −2 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -471,6 +476,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++) { Loading @@ -481,9 +542,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); } } } } Loading
core/java/android/app/BroadcastOptions.java +51 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. Loading Loading @@ -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. Loading @@ -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 {} Loading @@ -247,6 +256,14 @@ public class BroadcastOptions extends ComponentOptions { @SystemApi 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; Loading Loading @@ -297,6 +314,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); } /** Loading Loading @@ -774,6 +793,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) Loading @@ -781,6 +819,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() { Loading Loading @@ -831,6 +873,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; } }
core/java/android/content/Intent.java +15 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading
services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +37 −10 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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 Loading
services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java +99 −2 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -471,6 +476,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++) { Loading @@ -481,9 +542,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); } } } }