Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 755b166f authored by Sudheer Shanka's avatar Sudheer Shanka Committed by Android (Google) Code Review
Browse files

Merge "Allow senders to specify "merged" delivery group policy."

parents c48e9e90 c2a19507
Loading
Loading
Loading
Loading
+51 −0
Original line number Diff line number Diff line
@@ -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 {}
@@ -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;
@@ -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);
    }

    /**
@@ -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)
@@ -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() {
@@ -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;
    }
}
+15 −0
Original line number Diff line number Diff line
@@ -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
+37 −10
Original line number Diff line number Diff line
@@ -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
+99 −2
Original line number Diff line number Diff line
@@ -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;
@@ -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++) {
@@ -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);
            }
        }
    }
}