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

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

Merge "Allow senders to specify delivery group policies."

parents 4ce2768c d1712e45
Loading
Loading
Loading
Loading
+91 −0
Original line number Diff line number Diff line
@@ -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;
    }
}
+12 −0
Original line number Diff line number Diff line
@@ -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
+10 −0
Original line number Diff line number Diff line
@@ -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) {
+84 −0
Original line number Diff line number Diff line
@@ -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());
    }
}