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

Commit 1c5c2896 authored by Jeff Sharkey's avatar Jeff Sharkey Committed by Android (Google) Code Review
Browse files

Merge "Prioritize some broadcasts ahead of strict FIFO"

parents f309064d 0b35f9df
Loading
Loading
Loading
Loading
+33 −0
Original line number Diff line number Diff line
@@ -62,6 +62,7 @@ public class BroadcastOptions extends ComponentOptions {
    private long mRequireCompatChangeId = CHANGE_INVALID;
    private boolean mRequireCompatChangeEnabled = true;
    private boolean mIsAlarmBroadcast = false;
    private boolean mIsInteractiveBroadcast = false;
    private long mIdForResponseEvent;
    private @Nullable IntentFilter mRemoveMatchingFilter;
    private @DeliveryGroupPolicy int mDeliveryGroupPolicy;
@@ -167,6 +168,13 @@ public class BroadcastOptions extends ComponentOptions {
    public static final String KEY_ALARM_BROADCAST =
            "android:broadcast.is_alarm";

    /**
     * Corresponds to {@link #setInteractiveBroadcast(boolean)}
     * @hide
     */
    public static final String KEY_INTERACTIVE_BROADCAST =
            "android:broadcast.is_interactive";

    /**
     * @hide
     * @deprecated Use {@link android.os.PowerExemptionManager#
@@ -281,6 +289,7 @@ public class BroadcastOptions extends ComponentOptions {
        mRequireCompatChangeEnabled = opts.getBoolean(KEY_REQUIRE_COMPAT_CHANGE_ENABLED, true);
        mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT);
        mIsAlarmBroadcast = opts.getBoolean(KEY_ALARM_BROADCAST, false);
        mIsInteractiveBroadcast = opts.getBoolean(KEY_INTERACTIVE_BROADCAST, false);
        mRemoveMatchingFilter = opts.getParcelable(KEY_REMOVE_MATCHING_FILTER,
                IntentFilter.class);
        mDeliveryGroupPolicy = opts.getInt(KEY_DELIVERY_GROUP_POLICY,
@@ -598,6 +607,27 @@ public class BroadcastOptions extends ComponentOptions {
        return mIsAlarmBroadcast;
    }

    /**
     * When set, this broadcast will be understood as having originated from
     * some direct interaction by the user such as a notification tap or button
     * press.  Only the OS itself may use this option.
     * @hide
     * @param broadcastIsInteractive
     * @see #isInteractiveBroadcast()
     */
    public void setInteractiveBroadcast(boolean broadcastIsInteractive) {
        mIsInteractiveBroadcast = broadcastIsInteractive;
    }

    /**
     * Did this broadcast originate with a direct user interaction?
     * @return true if this broadcast is the result of an interaction, false otherwise
     * @hide
     */
    public boolean isInteractiveBroadcast() {
        return mIsInteractiveBroadcast;
    }

    /**
     * Did this broadcast originate from a push message from the server?
     *
@@ -743,6 +773,9 @@ public class BroadcastOptions extends ComponentOptions {
        if (mIsAlarmBroadcast) {
            b.putBoolean(KEY_ALARM_BROADCAST, true);
        }
        if (mIsInteractiveBroadcast) {
            b.putBoolean(KEY_INTERACTIVE_BROADCAST, true);
        }
        if (mMinManifestReceiverApiLevel != 0) {
            b.putInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, mMinManifestReceiverApiLevel);
        }
+4 −3
Original line number Diff line number Diff line
@@ -14700,13 +14700,14 @@ public class ActivityManagerService extends IActivityManager.Stub
            // Non-system callers can't declare that a broadcast is alarm-related.
            // The PendingIntent invocation case is handled in PendingIntentRecord.
            if (bOptions != null && callingUid != SYSTEM_UID) {
                if (bOptions.containsKey(BroadcastOptions.KEY_ALARM_BROADCAST)) {
                if (bOptions.containsKey(BroadcastOptions.KEY_ALARM_BROADCAST)
                        || bOptions.containsKey(BroadcastOptions.KEY_INTERACTIVE_BROADCAST)) {
                    if (DEBUG_BROADCAST) {
                        Slog.w(TAG, "Non-system caller " + callingUid
                                + " may not flag broadcast as alarm-related");
                                + " may not flag broadcast as alarm or interactive");
                    }
                    throw new SecurityException(
                            "Non-system callers may not flag broadcasts as alarm-related");
                            "Non-system callers may not flag broadcasts as alarm or interactive");
                }
            }
+112 −36
Original line number Diff line number Diff line
@@ -102,6 +102,13 @@ class BroadcastProcessQueue {
     */
    private final ArrayDeque<SomeArgs> mPending = new ArrayDeque<>();

    /**
     * Ordered collection of "urgent" broadcasts that are waiting to be
     * dispatched to this process, in the same representation as
     * {@link #mPending}.
     */
    private final ArrayDeque<SomeArgs> mPendingUrgent = new ArrayDeque<>();

    /**
     * Broadcast actively being dispatched to this process.
     */
@@ -172,9 +179,43 @@ class BroadcastProcessQueue {
     */
    public void enqueueOrReplaceBroadcast(@NonNull BroadcastRecord record, int recordIndex,
            int blockedUntilTerminalCount) {
        // If caller wants to replace, walk backwards looking for any matches
        if (record.isReplacePending()) {
            final Iterator<SomeArgs> it = mPending.descendingIterator();
            boolean didReplace = replaceBroadcastInQueue(mPending,
                    record, recordIndex, blockedUntilTerminalCount)
                    || replaceBroadcastInQueue(mPendingUrgent,
                    record, recordIndex, blockedUntilTerminalCount);
            if (didReplace) {
                return;
            }
        }

        // Caller isn't interested in replacing, or we didn't find any pending
        // item to replace above, so enqueue as a new broadcast
        SomeArgs newBroadcastArgs = SomeArgs.obtain();
        newBroadcastArgs.arg1 = record;
        newBroadcastArgs.argi1 = recordIndex;
        newBroadcastArgs.argi2 = blockedUntilTerminalCount;

        // Cross-broadcast prioritization policy:  some broadcasts might warrant being
        // issued ahead of others that are already pending, for example if this new
        // broadcast is in a different delivery class or is tied to a direct user interaction
        // with implicit responsiveness expectations.
        final ArrayDeque<SomeArgs> queue = record.isUrgent() ? mPendingUrgent : mPending;
        queue.addLast(newBroadcastArgs);
        onBroadcastEnqueued(record);
    }

    /**
     * Searches from newest to oldest, and at the first matching pending broadcast
     * it finds, replaces it in-place and returns -- does not attempt to handle
     * "duplicate" broadcasts in the queue.
     * <p>
     * @return {@code true} if it found and replaced an existing record in the queue;
     * {@code false} otherwise.
     */
    private boolean replaceBroadcastInQueue(@NonNull ArrayDeque<SomeArgs> queue,
            @NonNull BroadcastRecord record, int recordIndex,  int blockedUntilTerminalCount) {
        final Iterator<SomeArgs> it = queue.descendingIterator();
        final Object receiver = record.receivers.get(recordIndex);
        while (it.hasNext()) {
            final SomeArgs args = it.next();
@@ -190,19 +231,10 @@ class BroadcastProcessQueue {
                args.argi2 = blockedUntilTerminalCount;
                onBroadcastDequeued(testRecord);
                onBroadcastEnqueued(record);
                    return;
                }
                return true;
            }
        }

        // Caller isn't interested in replacing, or we didn't find any pending
        // item to replace above, so enqueue as a new broadcast
        SomeArgs args = SomeArgs.obtain();
        args.arg1 = record;
        args.argi1 = recordIndex;
        args.argi2 = blockedUntilTerminalCount;
        mPending.addLast(args);
        onBroadcastEnqueued(record);
        return false;
    }

    /**
@@ -233,8 +265,18 @@ class BroadcastProcessQueue {
     */
    public boolean forEachMatchingBroadcast(@NonNull BroadcastPredicate predicate,
            @NonNull BroadcastConsumer consumer, boolean andRemove) {
        boolean didSomething = forEachMatchingBroadcastInQueue(mPending,
                predicate, consumer, andRemove);
        didSomething |= forEachMatchingBroadcastInQueue(mPendingUrgent,
                predicate, consumer, andRemove);
        return didSomething;
    }

    private boolean forEachMatchingBroadcastInQueue(@NonNull ArrayDeque<SomeArgs> queue,
            @NonNull BroadcastPredicate predicate, @NonNull BroadcastConsumer consumer,
            boolean andRemove) {
        boolean didSomething = false;
        final Iterator<SomeArgs> it = mPending.iterator();
        final Iterator<SomeArgs> it = queue.iterator();
        while (it.hasNext()) {
            final SomeArgs args = it.next();
            final BroadcastRecord record = (BroadcastRecord) args.arg1;
@@ -309,7 +351,7 @@ class BroadcastProcessQueue {
     */
    public void makeActiveNextPending() {
        // TODO: what if the next broadcast isn't runnable yet?
        final SomeArgs next = mPending.removeFirst();
        final SomeArgs next = removeNextBroadcast();
        mActive = (BroadcastRecord) next.arg1;
        mActiveIndex = next.argi1;
        mActiveBlockedUntilTerminalCount = next.argi2;
@@ -413,13 +455,45 @@ class BroadcastProcessQueue {
    }

    public boolean isEmpty() {
        return mPending.isEmpty();
        return mPending.isEmpty() && mPendingUrgent.isEmpty();
    }

    public boolean isActive() {
        return mActive != null;
    }

    /**
     * Will thrown an exception if there are no pending broadcasts; relies on
     * {@link #isEmpty()} being false.
     */
    SomeArgs removeNextBroadcast() {
        ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
        return queue.removeFirst();
    }

    @Nullable ArrayDeque<SomeArgs> queueForNextBroadcast() {
        if (!mPendingUrgent.isEmpty()) {
            return mPendingUrgent;
        } else if (!mPending.isEmpty()) {
            return mPending;
        }
        return null;
    }

    /**
     * Returns null if there are no pending broadcasts
     */
    @Nullable SomeArgs peekNextBroadcast() {
        ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
        return (queue != null) ? queue.peekFirst() : null;
    }

    @VisibleForTesting
    @Nullable BroadcastRecord peekNextBroadcastRecord() {
        ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
        return (queue != null) ? (BroadcastRecord) queue.peekFirst().arg1 : null;
    }

    /**
     * Quickly determine if this queue has broadcasts that are still waiting to
     * be delivered at some point in the future.
@@ -437,11 +511,13 @@ class BroadcastProcessQueue {
            return mActive.enqueueTime > barrierTime;
        }
        final SomeArgs next = mPending.peekFirst();
        if (next != null) {
            return ((BroadcastRecord) next.arg1).enqueueTime > barrierTime;
        }
        // Nothing running or runnable means we're past the barrier
        return true;
        final SomeArgs nextUrgent = mPendingUrgent.peekFirst();
        // Empty queue is past any barrier
        final boolean nextLater = next == null
                || ((BroadcastRecord) next.arg1).enqueueTime > barrierTime;
        final boolean nextUrgentLater = nextUrgent == null
                || ((BroadcastRecord) nextUrgent.arg1).enqueueTime > barrierTime;
        return nextLater && nextUrgentLater;
    }

    public boolean isRunnable() {
@@ -519,7 +595,7 @@ class BroadcastProcessQueue {
     * Update {@link #getRunnableAt()} if it's currently invalidated.
     */
    private void updateRunnableAt() {
        final SomeArgs next = mPending.peekFirst();
        final SomeArgs next = peekNextBroadcast();
        if (next != null) {
            final BroadcastRecord r = (BroadcastRecord) next.arg1;
            final int index = next.argi1;
@@ -537,7 +613,7 @@ class BroadcastProcessQueue {

            // If we have too many broadcasts pending, bypass any delays that
            // might have been applied above to aid draining
            if (mPending.size() >= constants.MAX_PENDING_BROADCASTS) {
            if (mPending.size() + mPendingUrgent.size() >= constants.MAX_PENDING_BROADCASTS) {
                mRunnableAt = runnableAt;
                mRunnableAtReason = REASON_MAX_PENDING;
                return;
@@ -574,8 +650,8 @@ class BroadcastProcessQueue {
     */
    public void checkHealthLocked() {
        if (mRunnableAtReason == REASON_BLOCKED) {
            final SomeArgs next = mPending.peekFirst();
            Objects.requireNonNull(next, "peekFirst");
            final SomeArgs next = peekNextBroadcast();
            Objects.requireNonNull(next, "peekNextBroadcast");

            // If blocked more than 10 minutes, we're likely wedged
            final BroadcastRecord r = (BroadcastRecord) next.arg1;
+15 −0
Original line number Diff line number Diff line
@@ -83,6 +83,7 @@ final class BroadcastRecord extends Binder {
    final boolean alarm;    // originated from an alarm triggering?
    final boolean pushMessage; // originated from a push message?
    final boolean pushMessageOverQuota; // originated from a push message which was over quota?
    final boolean interactive; // originated from user interaction?
    final boolean initialSticky; // initial broadcast from register to sticky?
    final boolean prioritized; // contains more than one priority tranche
    final int userId;       // user id this broadcast was for
@@ -392,6 +393,7 @@ final class BroadcastRecord extends Binder {
        alarm = options != null && options.isAlarmBroadcast();
        pushMessage = options != null && options.isPushMessagingBroadcast();
        pushMessageOverQuota = options != null && options.isPushMessagingOverQuotaBroadcast();
        interactive = options != null && options.isInteractiveBroadcast();
        this.filterExtrasForReceiver = filterExtrasForReceiver;
    }

@@ -450,6 +452,7 @@ final class BroadcastRecord extends Binder {
        alarm = from.alarm;
        pushMessage = from.pushMessage;
        pushMessageOverQuota = from.pushMessageOverQuota;
        interactive = from.interactive;
        filterExtrasForReceiver = from.filterExtrasForReceiver;
    }

@@ -611,6 +614,18 @@ final class BroadcastRecord extends Binder {
        return (intent.getFlags() & Intent.FLAG_RECEIVER_NO_ABORT) != 0;
    }

    /**
     * Core policy determination about this broadcast's delivery prioritization
     */
    boolean isUrgent() {
        // TODO: flags for controlling policy
        // TODO: migrate alarm-prioritization flag to BroadcastConstants
        return (isForeground()
                || interactive
                || alarm)
                && receivers.size() == 1;
    }

    @NonNull String getHostingRecordTriggerType() {
        if (alarm) {
            return HostingRecord.TRIGGER_TYPE_ALARM;
+4 −3
Original line number Diff line number Diff line
@@ -443,13 +443,14 @@ public final class PendingIntentRecord extends IIntentSender.Stub {
        // invocation side effects such as allowlisting.
        if (options != null && callingUid != Process.SYSTEM_UID
                && key.type == ActivityManager.INTENT_SENDER_BROADCAST) {
            if (options.containsKey(BroadcastOptions.KEY_ALARM_BROADCAST)) {
            if (options.containsKey(BroadcastOptions.KEY_ALARM_BROADCAST)
                    || options.containsKey(BroadcastOptions.KEY_INTERACTIVE_BROADCAST)) {
                if (DEBUG_BROADCAST_LIGHT) {
                    Slog.w(TAG, "Non-system caller " + callingUid
                            + " may not flag broadcast as alarm-related");
                            + " may not flag broadcast as alarm or interactive");
                }
                throw new SecurityException(
                        "Non-system callers may not flag broadcasts as alarm-related");
                        "Non-system callers may not flag broadcasts as alarm or interactive");
            }
        }

Loading