blob: 1586e334d332d29d3cb6c9a46530504df5df76b3 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.am;
import static com.android.internal.util.Preconditions.checkState;
import static com.android.server.am.BroadcastRecord.deliveryStateToString;
import static com.android.server.am.BroadcastRecord.isReceiverEquals;
import android.annotation.CheckResult;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UptimeMillisLong;
import android.app.ActivityManager;
import android.app.BroadcastOptions;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
import android.text.format.DateUtils;
import android.util.IndentingPrintWriter;
import android.util.TimeUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import dalvik.annotation.optimization.NeverCompile;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayDeque;
import java.util.Iterator;
import java.util.Objects;
/**
* Queue of pending {@link BroadcastRecord} entries intended for delivery to a
* specific process.
* <p>
* Each queue has a concept of being "runnable at" a particular time in the
* future, which supports arbitrarily pausing or delaying delivery on a
* per-process basis.
* <p>
* Internally each queue consists of a pending broadcasts which are waiting to
* be dispatched, and a single active broadcast which is currently being
* dispatched.
* <p>
* This entire class is marked as {@code NotThreadSafe} since it's the
* responsibility of the caller to always interact with a relevant lock held.
*/
// @NotThreadSafe
class BroadcastProcessQueue {
static final boolean VERBOSE = false;
final @NonNull BroadcastConstants constants;
final @NonNull String processName;
final int uid;
/**
* Linked list connection to another process under this {@link #uid} which
* has a different {@link #processName}.
*/
@Nullable BroadcastProcessQueue processNameNext;
/**
* Linked list connections to runnable process with lower and higher
* {@link #getRunnableAt()} times.
*/
@Nullable BroadcastProcessQueue runnableAtNext;
@Nullable BroadcastProcessQueue runnableAtPrev;
/**
* Currently known details about the target process; typically undefined
* when the process isn't actively running.
*/
@Nullable ProcessRecord app;
/**
* Track name to use for {@link Trace} events, defined as part of upgrading
* into a running slot.
*/
@Nullable String runningTraceTrackName;
/**
* Flag indicating if this process should be OOM adjusted, defined as part
* of upgrading into a running slot.
*/
boolean runningOomAdjusted;
/**
* True if a timer has been started against this queue.
*/
private boolean mTimeoutScheduled;
/**
* Snapshotted value of {@link ProcessRecord#getCpuDelayTime()}, typically
* used when deciding if we should extend the soft ANR timeout.
*
* Required when Flags.anrTimerServiceEnabled is false.
*/
long lastCpuDelayTime;
/**
* Snapshotted value of {@link ProcessStateRecord#getCurProcState()} before
* dispatching the current broadcast to the receiver in this process.
*/
int lastProcessState;
/**
* Ordered collection of broadcasts that are waiting to be dispatched to
* this process, as a pair of {@link BroadcastRecord} and the index into
* {@link BroadcastRecord#receivers} that represents the receiver.
*/
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<>(4);
/**
* Ordered collection of "offload" broadcasts that are waiting to be
* dispatched to this process, in the same representation as
* {@link #mPending}.
*/
private final ArrayDeque<SomeArgs> mPendingOffload = new ArrayDeque<>(4);
/**
* Broadcast actively being dispatched to this process.
*/
private @Nullable BroadcastRecord mActive;
/**
* Receiver actively being dispatched to in this process. This is an index
* into the {@link BroadcastRecord#receivers} list of {@link #mActive}.
*/
private int mActiveIndex;
/**
* Count of {@link #mActive} broadcasts that have been dispatched since this
* queue was last idle.
*/
private int mActiveCountSinceIdle;
/**
* Count of {@link #mActive} broadcasts with assumed delivery that have been dispatched
* since this queue was last idle.
*/
private int mActiveAssumedDeliveryCountSinceIdle;
/**
* Flag indicating that the currently active broadcast is being dispatched
* was scheduled via a cold start.
*/
private boolean mActiveViaColdStart;
/**
* Flag indicating that the currently active broadcast is being dispatched
* to a package that was in the stopped state.
*/
private boolean mActiveWasStopped;
/**
* Number of consecutive urgent broadcasts that have been dispatched
* since the last non-urgent dispatch.
*/
private int mActiveCountConsecutiveUrgent;
/**
* Number of consecutive normal broadcasts that have been dispatched
* since the last offload dispatch.
*/
private int mActiveCountConsecutiveNormal;
/**
* Count of pending broadcasts of these various flavors.
*/
private int mCountEnqueued;
private int mCountDeferred;
private int mCountForeground;
private int mCountForegroundDeferred;
private int mCountOrdered;
private int mCountAlarm;
private int mCountPrioritized;
private int mCountPrioritizedDeferred;
private int mCountInteractive;
private int mCountResultTo;
private int mCountInstrumented;
private int mCountManifest;
private int mCountPrioritizeEarliestRequests;
private @UptimeMillisLong long mRunnableAt = Long.MAX_VALUE;
private @Reason int mRunnableAtReason = REASON_EMPTY;
private boolean mRunnableAtInvalidated;
/**
* Last state applied by {@link #updateDeferredStates}, used to quickly
* determine if a state transition is occurring.
*/
private boolean mLastDeferredStates;
private boolean mUidForeground;
private boolean mProcessFreezable;
private boolean mProcessInstrumented;
private boolean mProcessPersistent;
private String mCachedToString;
private String mCachedToShortString;
/**
* The duration by which any broadcasts to this process need to be delayed
*/
private long mForcedDelayedDurationMs;
public BroadcastProcessQueue(@NonNull BroadcastConstants constants,
@NonNull String processName, int uid) {
this.constants = Objects.requireNonNull(constants);
this.processName = Objects.requireNonNull(processName);
this.uid = uid;
}
private @NonNull ArrayDeque<SomeArgs> getQueueForBroadcast(@NonNull BroadcastRecord record) {
if (record.isUrgent()) {
return mPendingUrgent;
} else if (record.isOffload()) {
return mPendingOffload;
} else {
return mPending;
}
}
/**
* Enqueue the given broadcast to be dispatched to this process at some
* future point in time. The target receiver is indicated by the given index
* into {@link BroadcastRecord#receivers}.
* <p>
* If the broadcast is marked as {@link BroadcastRecord#isReplacePending()},
* then this call will replace any pending dispatch; otherwise it will
* enqueue as a normal broadcast.
* <p>
* When defined, this receiver is considered "blocked" until at least the
* given count of other receivers have reached a terminal state; typically
* used for ordered broadcasts and priority traunches.
*
* @return the existing broadcast record in the queue that was replaced with a newer broadcast
* sent with {@link Intent#FLAG_RECEIVER_REPLACE_PENDING} or {@code null} if there
* wasn't any broadcast that was replaced.
*/
@Nullable
public BroadcastRecord enqueueOrReplaceBroadcast(@NonNull BroadcastRecord record,
int recordIndex, @NonNull BroadcastConsumer deferredStatesApplyConsumer) {
// Ignore FLAG_RECEIVER_REPLACE_PENDING if the sender specified the policy using the
// BroadcastOptions delivery group APIs.
if (record.isReplacePending()
&& record.getDeliveryGroupPolicy() == BroadcastOptions.DELIVERY_GROUP_POLICY_ALL) {
final BroadcastRecord replacedBroadcastRecord = replaceBroadcast(record, recordIndex);
if (replacedBroadcastRecord != null) {
if (mLastDeferredStates && shouldBeDeferred()
&& (record.getDeliveryState(recordIndex)
== BroadcastRecord.DELIVERY_PENDING)) {
deferredStatesApplyConsumer.accept(record, recordIndex);
}
return replacedBroadcastRecord;
}
}
// 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;
// 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.
getQueueForBroadcast(record).addLast(newBroadcastArgs);
onBroadcastEnqueued(record, recordIndex);
// When updateDeferredStates() has already applied a deferred state to
// all pending items, apply to this new broadcast too
if (mLastDeferredStates && shouldBeDeferred()
&& (record.getDeliveryState(recordIndex) == BroadcastRecord.DELIVERY_PENDING)) {
deferredStatesApplyConsumer.accept(record, recordIndex);
}
return null;
}
/**
* Re-enqueue the active broadcast so that it can be made active and delivered again. In order
* to keep its previous position same to avoid issues with reordering, insert it at the head
* of the queue.
*
* Callers are responsible for clearing the active broadcast by calling
* {@link #makeActiveIdle()} after re-enqueuing it.
*/
public void reEnqueueActiveBroadcast() {
final BroadcastRecord record = getActive();
final int recordIndex = getActiveIndex();
final SomeArgs broadcastArgs = SomeArgs.obtain();
broadcastArgs.arg1 = record;
broadcastArgs.argi1 = recordIndex;
getQueueForBroadcast(record).addFirst(broadcastArgs);
onBroadcastEnqueued(record, recordIndex);
}
/**
* Searches from newest to oldest in the pending broadcast queues, 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.
*
* @return the existing broadcast record in the queue that was replaced with a newer broadcast
* sent with {@link Intent#FLAG_RECEIVER_REPLACE_PENDING} or {@code null} if there
* wasn't any broadcast that was replaced.
*/
@Nullable
private BroadcastRecord replaceBroadcast(@NonNull BroadcastRecord record, int recordIndex) {
final ArrayDeque<SomeArgs> queue = getQueueForBroadcast(record);
return replaceBroadcastInQueue(queue, record, recordIndex);
}
/**
* 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.
*
* @return the existing broadcast record in the queue that was replaced with a newer broadcast
* sent with {@link Intent#FLAG_RECEIVER_REPLACE_PENDING} or {@code null} if there
* wasn't any broadcast that was replaced.
*/
@Nullable
private BroadcastRecord replaceBroadcastInQueue(@NonNull ArrayDeque<SomeArgs> queue,
@NonNull BroadcastRecord record, int recordIndex) {
final Iterator<SomeArgs> it = queue.descendingIterator();
final Object receiver = record.receivers.get(recordIndex);
while (it.hasNext()) {
final SomeArgs args = it.next();
final BroadcastRecord testRecord = (BroadcastRecord) args.arg1;
final int testRecordIndex = args.argi1;
final Object testReceiver = testRecord.receivers.get(testRecordIndex);
// If we come across the record that's being enqueued in the queue, then that means
// we already enqueued it for a receiver in this process and trying to insert a new
// one past this could create priority inversion in the queue, so bail out.
if (record == testRecord && record.blockedUntilBeyondCount[recordIndex]
> testRecord.blockedUntilBeyondCount[testRecordIndex]) {
break;
}
if ((record.callingUid == testRecord.callingUid)
&& (record.userId == testRecord.userId)
&& record.intent.filterEquals(testRecord.intent)
&& isReceiverEquals(receiver, testReceiver)
&& testRecord.allReceiversPending()
&& record.isMatchingRecord(testRecord)) {
// Exact match found; perform in-place swap
args.arg1 = record;
args.argi1 = recordIndex;
record.copyEnqueueTimeFrom(testRecord);
onBroadcastDequeued(testRecord, testRecordIndex);
onBroadcastEnqueued(record, recordIndex);
return testRecord;
}
}
return null;
}
/**
* Functional interface that tests a {@link BroadcastRecord} that has been
* previously enqueued in {@link BroadcastProcessQueue}.
*/
@FunctionalInterface
public interface BroadcastPredicate {
boolean test(@NonNull BroadcastRecord r, int index);
}
/**
* Functional interface that consumes a {@link BroadcastRecord} that has
* been previously enqueued in {@link BroadcastProcessQueue}.
*/
@FunctionalInterface
public interface BroadcastConsumer {
void accept(@NonNull BroadcastRecord r, int index);
}
/**
* Invoke given consumer for any broadcasts matching given predicate. If
* requested, matching broadcasts will also be removed from this queue.
* <p>
* Predicates that choose to remove a broadcast <em>must</em> finish
* delivery of the matched broadcast, to ensure that situations like ordered
* broadcasts are handled consistently.
*
* @return if this operation may have changed internal state, indicating
* that the caller is responsible for invoking
* {@link BroadcastQueueModernImpl#updateRunnableList}
*/
@CheckResult
public boolean forEachMatchingBroadcast(@NonNull BroadcastPredicate predicate,
@NonNull BroadcastConsumer consumer, boolean andRemove) {
boolean didSomething = false;
didSomething |= forEachMatchingBroadcastInQueue(mPending,
predicate, consumer, andRemove);
didSomething |= forEachMatchingBroadcastInQueue(mPendingUrgent,
predicate, consumer, andRemove);
didSomething |= forEachMatchingBroadcastInQueue(mPendingOffload,
predicate, consumer, andRemove);
return didSomething;
}
@CheckResult
private boolean forEachMatchingBroadcastInQueue(@NonNull ArrayDeque<SomeArgs> queue,
@NonNull BroadcastPredicate predicate, @NonNull BroadcastConsumer consumer,
boolean andRemove) {
boolean didSomething = false;
final Iterator<SomeArgs> it = queue.iterator();
while (it.hasNext()) {
final SomeArgs args = it.next();
final BroadcastRecord record = (BroadcastRecord) args.arg1;
final int recordIndex = args.argi1;
if (predicate.test(record, recordIndex)) {
consumer.accept(record, recordIndex);
if (andRemove) {
args.recycle();
it.remove();
onBroadcastDequeued(record, recordIndex);
} else {
// Even if we're leaving broadcast in queue, it may have
// been mutated in such a way to change our runnable time
invalidateRunnableAt();
}
didSomething = true;
}
}
// TODO: also check any active broadcast once we have a better "nonce"
// representing each scheduled broadcast to avoid races
return didSomething;
}
/**
* Update the actively running "warm" process for this process.
*
* @return if this operation may have changed internal state, indicating
* that the caller is responsible for invoking
* {@link BroadcastQueueModernImpl#updateRunnableList}
*/
@CheckResult
public boolean setProcessAndUidState(@Nullable ProcessRecord app, boolean uidForeground,
boolean processFreezable) {
this.app = app;
// Since we may have just changed our PID, invalidate cached strings
mCachedToString = null;
mCachedToShortString = null;
boolean didSomething = false;
if (app != null) {
didSomething |= setUidForeground(uidForeground);
didSomething |= setProcessFreezable(processFreezable);
didSomething |= setProcessInstrumented(app.getActiveInstrumentation() != null);
didSomething |= setProcessPersistent(app.isPersistent());
} else {
didSomething |= setUidForeground(false);
didSomething |= setProcessFreezable(false);
didSomething |= setProcessInstrumented(false);
didSomething |= setProcessPersistent(false);
}
return didSomething;
}
/**
* Update if the UID this process is belongs to is in "foreground" state, which signals
* broadcast dispatch should prioritize delivering broadcasts to this process to minimize any
* delays in UI updates.
*/
@CheckResult
private boolean setUidForeground(boolean uidForeground) {
if (mUidForeground != uidForeground) {
mUidForeground = uidForeground;
invalidateRunnableAt();
return true;
} else {
return false;
}
}
/**
* Update if this process is in the "freezable" state, typically signaling that
* broadcast dispatch should be paused or delayed.
*/
@CheckResult
private boolean setProcessFreezable(boolean freezable) {
if (mProcessFreezable != freezable) {
mProcessFreezable = freezable;
invalidateRunnableAt();
return true;
} else {
return false;
}
}
/**
* Update if this process is in the "instrumented" state, typically
* signaling that broadcast dispatch should bypass all pauses or delays, to
* avoid holding up test suites.
*/
@CheckResult
private boolean setProcessInstrumented(boolean instrumented) {
if (mProcessInstrumented != instrumented) {
mProcessInstrumented = instrumented;
invalidateRunnableAt();
return true;
} else {
return false;
}
}
/**
* Update if this process is in the "persistent" state, which signals broadcast dispatch should
* bypass all pauses or delays to prevent the system from becoming out of sync with itself.
*/
@CheckResult
private boolean setProcessPersistent(boolean persistent) {
if (mProcessPersistent != persistent) {
mProcessPersistent = persistent;
invalidateRunnableAt();
return true;
} else {
return false;
}
}
/**
* Return if we know of an actively running "warm" process for this queue.
*/
public boolean isProcessWarm() {
return (app != null) && (app.getOnewayThread() != null) && !app.isKilled();
}
public int getPreferredSchedulingGroupLocked() {
if (!isActive()) {
return ProcessList.SCHED_GROUP_UNDEFINED;
} else if (mCountForeground > mCountForegroundDeferred) {
// We have a foreground broadcast somewhere down the queue, so
// boost priority until we drain them all
return ProcessList.SCHED_GROUP_DEFAULT;
} else if ((mActive != null) && mActive.isForeground()) {
// We have a foreground broadcast right now, so boost priority
return ProcessList.SCHED_GROUP_DEFAULT;
} else {
return ProcessList.SCHED_GROUP_BACKGROUND;
}
}
/**
* Count of {@link #mActive} broadcasts that have been dispatched since this
* queue was last idle.
*/
public int getActiveCountSinceIdle() {
return mActiveCountSinceIdle;
}
/**
* Count of {@link #mActive} broadcasts with assumed delivery that have been dispatched
* since this queue was last idle.
*/
public int getActiveAssumedDeliveryCountSinceIdle() {
return mActiveAssumedDeliveryCountSinceIdle;
}
public void setActiveViaColdStart(boolean activeViaColdStart) {
mActiveViaColdStart = activeViaColdStart;
}
public void setActiveWasStopped(boolean activeWasStopped) {
mActiveWasStopped = activeWasStopped;
}
public boolean getActiveViaColdStart() {
return mActiveViaColdStart;
}
public boolean getActiveWasStopped() {
return mActiveWasStopped;
}
/**
* Get package name of the first application loaded into this process.
*/
@Nullable
public String getPackageName() {
return app == null ? null : app.getApplicationInfo().packageName;
}
/**
* Set the currently active broadcast to the next pending broadcast.
*/
public void makeActiveNextPending() {
// TODO: what if the next broadcast isn't runnable yet?
final SomeArgs next = removeNextBroadcast();
mActive = (BroadcastRecord) next.arg1;
mActiveIndex = next.argi1;
mActiveCountSinceIdle++;
mActiveAssumedDeliveryCountSinceIdle +=
(mActive.isAssumedDelivered(mActiveIndex) ? 1 : 0);
mActiveViaColdStart = false;
mActiveWasStopped = false;
next.recycle();
onBroadcastDequeued(mActive, mActiveIndex);
}
/**
* Set the currently running broadcast to be idle.
*/
public void makeActiveIdle() {
mActive = null;
mActiveIndex = 0;
mActiveCountSinceIdle = 0;
mActiveAssumedDeliveryCountSinceIdle = 0;
mActiveViaColdStart = false;
invalidateRunnableAt();
}
/**
* Update summary statistics when the given record has been enqueued.
*/
private void onBroadcastEnqueued(@NonNull BroadcastRecord record, int recordIndex) {
mCountEnqueued++;
if (record.deferUntilActive) {
mCountDeferred++;
}
if (record.isForeground()) {
if (record.deferUntilActive) {
mCountForegroundDeferred++;
}
mCountForeground++;
}
if (record.ordered) {
mCountOrdered++;
}
if (record.alarm) {
mCountAlarm++;
}
if (record.prioritized) {
if (record.deferUntilActive) {
mCountPrioritizedDeferred++;
}
mCountPrioritized++;
}
if (record.interactive) {
mCountInteractive++;
}
if (record.resultTo != null) {
mCountResultTo++;
}
if (record.callerInstrumented) {
mCountInstrumented++;
}
if (record.receivers.get(recordIndex) instanceof ResolveInfo) {
mCountManifest++;
}
invalidateRunnableAt();
}
/**
* Update summary statistics when the given record has been dequeued.
*/
private void onBroadcastDequeued(@NonNull BroadcastRecord record, int recordIndex) {
mCountEnqueued--;
if (record.deferUntilActive) {
mCountDeferred--;
}
if (record.isForeground()) {
if (record.deferUntilActive) {
mCountForegroundDeferred--;
}
mCountForeground--;
}
if (record.ordered) {
mCountOrdered--;
}
if (record.alarm) {
mCountAlarm--;
}
if (record.prioritized) {
if (record.deferUntilActive) {
mCountPrioritizedDeferred--;
}
mCountPrioritized--;
}
if (record.interactive) {
mCountInteractive--;
}
if (record.resultTo != null) {
mCountResultTo--;
}
if (record.callerInstrumented) {
mCountInstrumented--;
}
if (record.receivers.get(recordIndex) instanceof ResolveInfo) {
mCountManifest--;
}
invalidateRunnableAt();
}
public void traceProcessStartingBegin() {
Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
runningTraceTrackName, toShortString() + " starting", hashCode());
}
public void traceProcessRunningBegin() {
Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
runningTraceTrackName, toShortString() + " running", hashCode());
}
public void traceProcessEnd() {
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
runningTraceTrackName, hashCode());
}
public void traceActiveBegin() {
Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
runningTraceTrackName, mActive.toShortString() + " scheduled", hashCode());
}
public void traceActiveEnd() {
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
runningTraceTrackName, hashCode());
}
/**
* Return the broadcast being actively dispatched in this process.
*/
public @NonNull BroadcastRecord getActive() {
return Objects.requireNonNull(mActive);
}
/**
* Return the index into {@link BroadcastRecord#receivers} of the receiver
* being actively dispatched in this process.
*/
public int getActiveIndex() {
Objects.requireNonNull(mActive);
return mActiveIndex;
}
public boolean isEmpty() {
return mPending.isEmpty() && mPendingUrgent.isEmpty() && mPendingOffload.isEmpty();
}
public boolean isActive() {
return mActive != null;
}
/**
* @return if this operation may have changed internal state, indicating
* that the caller is responsible for invoking
* {@link BroadcastQueueModernImpl#updateRunnableList}
*/
@CheckResult
boolean forceDelayBroadcastDelivery(long delayedDurationMs) {
if (mForcedDelayedDurationMs != delayedDurationMs) {
mForcedDelayedDurationMs = delayedDurationMs;
invalidateRunnableAt();
return true;
} else {
return false;
}
}
/**
* Will thrown an exception if there are no pending broadcasts; relies on
* {@link #isEmpty()} being false.
*/
private @Nullable SomeArgs removeNextBroadcast() {
final ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
if (queue == mPendingUrgent) {
mActiveCountConsecutiveUrgent++;
} else if (queue == mPending) {
mActiveCountConsecutiveUrgent = 0;
mActiveCountConsecutiveNormal++;
} else if (queue == mPendingOffload) {
mActiveCountConsecutiveUrgent = 0;
mActiveCountConsecutiveNormal = 0;
}
return !isQueueEmpty(queue) ? queue.removeFirst() : null;
}
@Nullable ArrayDeque<SomeArgs> queueForNextBroadcast() {
final ArrayDeque<SomeArgs> nextNormal = queueForNextBroadcast(
mPending, mPendingOffload,
mActiveCountConsecutiveNormal, constants.MAX_CONSECUTIVE_NORMAL_DISPATCHES);
final ArrayDeque<SomeArgs> nextBroadcastQueue = queueForNextBroadcast(
mPendingUrgent, nextNormal,
mActiveCountConsecutiveUrgent, constants.MAX_CONSECUTIVE_URGENT_DISPATCHES);
return nextBroadcastQueue;
}
private @Nullable ArrayDeque<SomeArgs> queueForNextBroadcast(
@Nullable ArrayDeque<SomeArgs> highPriorityQueue,
@Nullable ArrayDeque<SomeArgs> lowPriorityQueue,
int consecutiveHighPriorityCount,
int maxHighPriorityDispatchLimit) {
// nothing high priority pending, no further decisionmaking
if (isQueueEmpty(highPriorityQueue)) {
return lowPriorityQueue;
}
// nothing but high priority pending, also no further decisionmaking
if (isQueueEmpty(lowPriorityQueue)) {
return highPriorityQueue;
}
// Starvation mitigation: although we prioritize high priority queues by default,
// we allow low priority queues to make steady progress even if broadcasts in
// high priority queue are arriving faster than they can be dispatched.
//
// We do not try to defer to the next broadcast in low priority queues if that broadcast
// is ordered and still blocked on delivery to other recipients.
final SomeArgs nextLPArgs = lowPriorityQueue.peekFirst();
final BroadcastRecord nextLPRecord = (BroadcastRecord) nextLPArgs.arg1;
final int nextLPRecordIndex = nextLPArgs.argi1;
final BroadcastRecord nextHPRecord = (BroadcastRecord) highPriorityQueue.peekFirst().arg1;
final boolean shouldConsiderLPQueue = (mCountPrioritizeEarliestRequests > 0
|| consecutiveHighPriorityCount >= maxHighPriorityDispatchLimit);
final boolean isLPQueueEligible = shouldConsiderLPQueue
&& nextLPRecord.enqueueTime <= nextHPRecord.enqueueTime
&& !nextLPRecord.isBlocked(nextLPRecordIndex);
return isLPQueueEligible ? lowPriorityQueue : highPriorityQueue;
}
private static boolean isQueueEmpty(@Nullable ArrayDeque<SomeArgs> queue) {
return (queue == null || queue.isEmpty());
}
/**
* Add a request to prioritize dispatching of broadcasts that have been enqueued the earliest,
* even if there are urgent broadcasts waiting to be dispatched. This is typically used in
* case there are callers waiting for "barrier" to be reached.
*
* @return if this operation may have changed internal state, indicating
* that the caller is responsible for invoking
* {@link BroadcastQueueModernImpl#updateRunnableList}
*/
@CheckResult
@VisibleForTesting
boolean addPrioritizeEarliestRequest() {
if (mCountPrioritizeEarliestRequests == 0) {
mCountPrioritizeEarliestRequests++;
invalidateRunnableAt();
return true;
} else {
mCountPrioritizeEarliestRequests++;
return false;
}
}
/**
* Remove a request to prioritize dispatching of broadcasts that have been enqueued the
* earliest, even if there are urgent broadcasts waiting to be dispatched. This is typically
* used in case there are callers waiting for "barrier" to be reached.
*
* <p> Once there are no more remaining requests, the dispatching order reverts back to normal.
*
* @return if this operation may have changed internal state, indicating
* that the caller is responsible for invoking
* {@link BroadcastQueueModernImpl#updateRunnableList}
*/
@CheckResult
boolean removePrioritizeEarliestRequest() {
mCountPrioritizeEarliestRequests--;
if (mCountPrioritizeEarliestRequests == 0) {
invalidateRunnableAt();
return true;
} else if (mCountPrioritizeEarliestRequests < 0) {
mCountPrioritizeEarliestRequests = 0;
return false;
} else {
return false;
}
}
/**
* Returns null if there are no pending broadcasts
*/
@Nullable SomeArgs peekNextBroadcast() {
ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
return !isQueueEmpty(queue) ? queue.peekFirst() : null;
}
@VisibleForTesting
@Nullable BroadcastRecord peekNextBroadcastRecord() {
ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
return !isQueueEmpty(queue) ? (BroadcastRecord) queue.peekFirst().arg1 : null;
}
/**
* Quickly determine if this queue has broadcasts waiting to be delivered to
* manifest receivers, which indicates we should request an OOM adjust.
*/
public boolean isPendingManifest() {
return mCountManifest > 0;
}
/**
* Quickly determine if this queue has ordered broadcasts waiting to be delivered,
* which indicates we should request an OOM adjust.
*/
public boolean isPendingOrdered() {
return mCountOrdered > 0;
}
/**
* Quickly determine if this queue has broadcasts waiting to be delivered for which result is
* expected from the senders, which indicates we should request an OOM adjust.
*/
public boolean isPendingResultTo() {
return mCountResultTo > 0;
}
/**
* Report whether this queue is currently handling an urgent broadcast.
*/
public boolean isPendingUrgent() {
BroadcastRecord next = peekNextBroadcastRecord();
return (next != null) ? next.isUrgent() : false;
}
/**
* Quickly determine if this queue has broadcasts that are still waiting to
* be delivered at some point in the future.
*/
public boolean isIdle() {
return (!isActive() && isEmpty()) || isDeferredUntilActive();
}
/**
* Quickly determine if this queue has non-deferred broadcasts enqueued before the given
* barrier timestamp that are still waiting to be delivered.
*/
public boolean isBeyondBarrierLocked(@UptimeMillisLong long barrierTime) {
final SomeArgs next = mPending.peekFirst();
final SomeArgs nextUrgent = mPendingUrgent.peekFirst();
final SomeArgs nextOffload = mPendingOffload.peekFirst();
// Empty records are always past any barrier
final boolean activeBeyond = (mActive == null)
|| mActive.enqueueTime > barrierTime;
final boolean nextBeyond = (next == null)
|| ((BroadcastRecord) next.arg1).enqueueTime > barrierTime;
final boolean nextUrgentBeyond = (nextUrgent == null)
|| ((BroadcastRecord) nextUrgent.arg1).enqueueTime > barrierTime;
final boolean nextOffloadBeyond = (nextOffload == null)
|| ((BroadcastRecord) nextOffload.arg1).enqueueTime > barrierTime;
return (activeBeyond && nextBeyond && nextUrgentBeyond && nextOffloadBeyond)
|| isDeferredUntilActive();
}
/**
* Quickly determine if this queue has non-deferred broadcasts waiting to be dispatched,
* that match {@code intent}, as defined by {@link Intent#filterEquals(Intent)}.
*/
public boolean isDispatched(@NonNull Intent intent) {
final boolean activeDispatched = (mActive == null)
|| (!intent.filterEquals(mActive.intent));
final boolean dispatched = isDispatchedInQueue(mPending, intent);
final boolean urgentDispatched = isDispatchedInQueue(mPendingUrgent, intent);
final boolean offloadDispatched = isDispatchedInQueue(mPendingOffload, intent);
return (activeDispatched && dispatched && urgentDispatched && offloadDispatched)
|| isDeferredUntilActive();
}
/**
* Quickly determine if the {@code queue} has non-deferred broadcasts waiting to be dispatched,
* that match {@code intent}, as defined by {@link Intent#filterEquals(Intent)}.
*/
private boolean isDispatchedInQueue(@NonNull ArrayDeque<SomeArgs> queue,
@NonNull Intent intent) {
final Iterator<SomeArgs> it = queue.iterator();
while (it.hasNext()) {
final SomeArgs args = it.next();
if (args == null) {
return true;
}
final BroadcastRecord record = (BroadcastRecord) args.arg1;
if (intent.filterEquals(record.intent)) {
return false;
}
}
return true;
}
public boolean isRunnable() {
if (mRunnableAtInvalidated) updateRunnableAt();
return mRunnableAt != Long.MAX_VALUE;
}
public boolean isDeferredUntilActive() {
if (mRunnableAtInvalidated) updateRunnableAt();
return mRunnableAtReason == BroadcastProcessQueue.REASON_CACHED_INFINITE_DEFER;
}
public boolean hasDeferredBroadcasts() {
return (mCountDeferred > 0);
}
/**
* Return time at which this process is considered runnable. This is
* typically the time at which the next pending broadcast was first
* enqueued, but it also reflects any pauses or delays that should be
* applied to the process.
* <p>
* Returns {@link Long#MAX_VALUE} when this queue isn't currently runnable,
* typically when the queue is empty or when paused.
*/
public @UptimeMillisLong long getRunnableAt() {
if (mRunnableAtInvalidated) updateRunnableAt();
return mRunnableAt;
}
/**
* Return the "reason" behind the current {@link #getRunnableAt()} value,
* such as indicating why the queue is being delayed or paused.
*/
public @Reason int getRunnableAtReason() {
if (mRunnableAtInvalidated) updateRunnableAt();
return mRunnableAtReason;
}
public void invalidateRunnableAt() {
mRunnableAtInvalidated = true;
}
static final int REASON_EMPTY = 0;
static final int REASON_CACHED = 1;
static final int REASON_NORMAL = 2;
static final int REASON_MAX_PENDING = 3;
static final int REASON_BLOCKED = 4;
static final int REASON_INSTRUMENTED = 5;
static final int REASON_PERSISTENT = 6;
static final int REASON_FORCE_DELAYED = 7;
static final int REASON_CACHED_INFINITE_DEFER = 8;
static final int REASON_CONTAINS_FOREGROUND = 10;
static final int REASON_CONTAINS_ORDERED = 11;
static final int REASON_CONTAINS_ALARM = 12;
static final int REASON_CONTAINS_PRIORITIZED = 13;
static final int REASON_CONTAINS_INTERACTIVE = 14;
static final int REASON_CONTAINS_RESULT_TO = 15;
static final int REASON_CONTAINS_INSTRUMENTED = 16;
static final int REASON_CONTAINS_MANIFEST = 17;
static final int REASON_FOREGROUND = 18;
static final int REASON_CORE_UID = 19;
static final int REASON_TOP_PROCESS = 20;
@IntDef(flag = false, prefix = { "REASON_" }, value = {
REASON_EMPTY,
REASON_CACHED,
REASON_NORMAL,
REASON_MAX_PENDING,
REASON_BLOCKED,
REASON_INSTRUMENTED,
REASON_PERSISTENT,
REASON_FORCE_DELAYED,
REASON_CACHED_INFINITE_DEFER,
REASON_CONTAINS_FOREGROUND,
REASON_CONTAINS_ORDERED,
REASON_CONTAINS_ALARM,
REASON_CONTAINS_PRIORITIZED,
REASON_CONTAINS_INTERACTIVE,
REASON_CONTAINS_RESULT_TO,
REASON_CONTAINS_INSTRUMENTED,
REASON_CONTAINS_MANIFEST,
REASON_FOREGROUND,
REASON_CORE_UID,
REASON_TOP_PROCESS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Reason {}
static @NonNull String reasonToString(@Reason int reason) {
switch (reason) {
case REASON_EMPTY: return "EMPTY";
case REASON_CACHED: return "CACHED";
case REASON_NORMAL: return "NORMAL";
case REASON_MAX_PENDING: return "MAX_PENDING";
case REASON_BLOCKED: return "BLOCKED";
case REASON_INSTRUMENTED: return "INSTRUMENTED";
case REASON_PERSISTENT: return "PERSISTENT";
case REASON_FORCE_DELAYED: return "FORCE_DELAYED";
case REASON_CACHED_INFINITE_DEFER: return "INFINITE_DEFER";
case REASON_CONTAINS_FOREGROUND: return "CONTAINS_FOREGROUND";
case REASON_CONTAINS_ORDERED: return "CONTAINS_ORDERED";
case REASON_CONTAINS_ALARM: return "CONTAINS_ALARM";
case REASON_CONTAINS_PRIORITIZED: return "CONTAINS_PRIORITIZED";
case REASON_CONTAINS_INTERACTIVE: return "CONTAINS_INTERACTIVE";
case REASON_CONTAINS_RESULT_TO: return "CONTAINS_RESULT_TO";
case REASON_CONTAINS_INSTRUMENTED: return "CONTAINS_INSTRUMENTED";
case REASON_CONTAINS_MANIFEST: return "CONTAINS_MANIFEST";
case REASON_FOREGROUND: return "FOREGROUND";
case REASON_CORE_UID: return "CORE_UID";
case REASON_TOP_PROCESS: return "TOP_PROCESS";
default: return Integer.toString(reason);
}
}
/**
* Update {@link #getRunnableAt()}, when needed.
*/
void updateRunnableAt() {
if (!mRunnableAtInvalidated) return;
mRunnableAtInvalidated = false;
final SomeArgs next = peekNextBroadcast();
if (next != null) {
final BroadcastRecord r = (BroadcastRecord) next.arg1;
final int index = next.argi1;
final long runnableAt = r.enqueueTime;
if (r.isBlocked(index)) {
mRunnableAt = Long.MAX_VALUE;
mRunnableAtReason = REASON_BLOCKED;
return;
}
if (mForcedDelayedDurationMs > 0) {
mRunnableAt = runnableAt + mForcedDelayedDurationMs;
mRunnableAtReason = REASON_FORCE_DELAYED;
} else if (mCountForeground > mCountForegroundDeferred) {
mRunnableAt = runnableAt + constants.DELAY_URGENT_MILLIS;
mRunnableAtReason = REASON_CONTAINS_FOREGROUND;
} else if (mCountInteractive > 0) {
mRunnableAt = runnableAt + constants.DELAY_URGENT_MILLIS;
mRunnableAtReason = REASON_CONTAINS_INTERACTIVE;
} else if (mCountInstrumented > 0) {
mRunnableAt = runnableAt + constants.DELAY_URGENT_MILLIS;
mRunnableAtReason = REASON_CONTAINS_INSTRUMENTED;
} else if (mProcessInstrumented) {
mRunnableAt = runnableAt + constants.DELAY_URGENT_MILLIS;
mRunnableAtReason = REASON_INSTRUMENTED;
} else if (mUidForeground) {
mRunnableAt = runnableAt + constants.DELAY_FOREGROUND_PROC_MILLIS;
mRunnableAtReason = REASON_FOREGROUND;
} else if (app != null && app.getSetProcState() == ActivityManager.PROCESS_STATE_TOP) {
// TODO (b/287676625): Use a callback to check when a process goes in and out of
// the TOP state.
mRunnableAt = runnableAt + constants.DELAY_FOREGROUND_PROC_MILLIS;
mRunnableAtReason = REASON_TOP_PROCESS;
} else if (mProcessPersistent) {
mRunnableAt = runnableAt + constants.DELAY_PERSISTENT_PROC_MILLIS;
mRunnableAtReason = REASON_PERSISTENT;
} else if (mCountOrdered > 0) {
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_CONTAINS_ORDERED;
} else if (mCountAlarm > 0) {
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_CONTAINS_ALARM;
} else if (mCountPrioritized > mCountPrioritizedDeferred) {
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_CONTAINS_PRIORITIZED;
} else if (mCountManifest > 0) {
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_CONTAINS_MANIFEST;
} else if (mProcessFreezable) {
if (r.deferUntilActive) {
// All enqueued broadcasts are deferrable, defer
if (mCountDeferred == mCountEnqueued) {
mRunnableAt = Long.MAX_VALUE;
mRunnableAtReason = REASON_CACHED_INFINITE_DEFER;
} else {
// At least one enqueued broadcast isn't deferrable, repick time and reason
// for this record. If a later record is not deferrable and is one of these
// special cases, one of the cases above would have already caught that.
if (r.isForeground()) {
mRunnableAt = runnableAt + constants.DELAY_URGENT_MILLIS;
mRunnableAtReason = REASON_CONTAINS_FOREGROUND;
} else if (r.prioritized) {
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_CONTAINS_PRIORITIZED;
} else if (r.resultTo != null) {
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_CONTAINS_RESULT_TO;
} else {
mRunnableAt = runnableAt + constants.DELAY_CACHED_MILLIS;
mRunnableAtReason = REASON_CACHED;
}
}
} else {
// This record isn't deferrable
mRunnableAt = runnableAt + constants.DELAY_CACHED_MILLIS;
mRunnableAtReason = REASON_CACHED;
}
} else if (mCountResultTo > 0) {
// All resultTo broadcasts are infinitely deferrable, so if the app
// is already cached, they'll be deferred on the line above
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_CONTAINS_RESULT_TO;
} else if (UserHandle.isCore(uid)) {
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_CORE_UID;
} else {
mRunnableAt = runnableAt + constants.DELAY_NORMAL_MILLIS;
mRunnableAtReason = REASON_NORMAL;
}
// If we have too many broadcasts pending, bypass any delays that
// might have been applied above to aid draining
if (mPending.size() + mPendingUrgent.size()
+ mPendingOffload.size() >= constants.MAX_PENDING_BROADCASTS) {
mRunnableAt = Math.min(mRunnableAt, runnableAt);
mRunnableAtReason = REASON_MAX_PENDING;
}
if (VERBOSE) {
Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, "BroadcastQueue",
((app != null) ? app.processName : "(null)")
+ ":" + r.intent.toString() + ":"
+ r.deferUntilActive
+ ":" + mRunnableAt + " " + reasonToString(mRunnableAtReason)
+ ":" + ((app != null) ? app.isCached() : "false"));
}
} else {
mRunnableAt = Long.MAX_VALUE;
mRunnableAtReason = REASON_EMPTY;
}
}
/**
* Update {@link BroadcastRecord#DELIVERY_DEFERRED} states of all our
* pending broadcasts, when needed.
*/
void updateDeferredStates(@NonNull BroadcastConsumer applyConsumer,
@NonNull BroadcastConsumer clearConsumer) {
// When all we have pending is deferred broadcasts, and we're cached,
// then we want everything to be marked deferred
final boolean wantDeferredStates = shouldBeDeferred();
if (mLastDeferredStates != wantDeferredStates) {
mLastDeferredStates = wantDeferredStates;
if (wantDeferredStates) {
forEachMatchingBroadcast((r, i) -> {
return (r.getDeliveryState(i) == BroadcastRecord.DELIVERY_PENDING);
}, applyConsumer, false);
} else {
forEachMatchingBroadcast((r, i) -> {
return (r.getDeliveryState(i) == BroadcastRecord.DELIVERY_DEFERRED);
}, clearConsumer, false);
}
}
}
void clearDeferredStates(@NonNull BroadcastConsumer clearConsumer) {
if (mLastDeferredStates) {
mLastDeferredStates = false;
forEachMatchingBroadcast((r, i) -> {
return (r.getDeliveryState(i) == BroadcastRecord.DELIVERY_DEFERRED);
}, clearConsumer, false);
}
}
@VisibleForTesting
boolean shouldBeDeferred() {
if (mRunnableAtInvalidated) updateRunnableAt();
return mRunnableAtReason == REASON_CACHED
|| mRunnableAtReason == REASON_CACHED_INFINITE_DEFER;
}
/**
* Check overall health, confirming things are in a reasonable state and
* that we're not wedged.
*/
public void assertHealthLocked() {
// If we're not actively running, we should be sorted into the runnable
// list, and if we're invalidated then someone likely forgot to invoke
// updateRunnableList() to re-sort us into place
if (!isActive()) {
checkState(!mRunnableAtInvalidated, "mRunnableAtInvalidated");
}
assertHealthLocked(mPending);
assertHealthLocked(mPendingUrgent);
assertHealthLocked(mPendingOffload);
}
private void assertHealthLocked(@NonNull ArrayDeque<SomeArgs> queue) {
if (queue.isEmpty()) return;
final Iterator<SomeArgs> it = queue.descendingIterator();
while (it.hasNext()) {
final SomeArgs args = it.next();
final BroadcastRecord record = (BroadcastRecord) args.arg1;
final int recordIndex = args.argi1;
if (BroadcastRecord.isDeliveryStateTerminal(record.getDeliveryState(recordIndex))
|| record.isDeferUntilActive()) {
continue;
} else {
// If waiting more than 10 minutes, we're likely wedged
final long waitingTime = SystemClock.uptimeMillis() - record.enqueueTime;
checkState(waitingTime < (10 * DateUtils.MINUTE_IN_MILLIS), "waitingTime");
}
}
}
/**
* Insert the given queue into a sorted linked list of "runnable" queues.
*
* @param head the current linked list head
* @param item the queue to insert
* @return a potentially updated linked list head
*/
@VisibleForTesting
static @Nullable BroadcastProcessQueue insertIntoRunnableList(
@Nullable BroadcastProcessQueue head, @NonNull BroadcastProcessQueue item) {
if (head == null) {
return item;
}
final long itemRunnableAt = item.getRunnableAt();
BroadcastProcessQueue test = head;
BroadcastProcessQueue tail = null;
while (test != null) {
if (test.getRunnableAt() > itemRunnableAt) {
item.runnableAtNext = test;
item.runnableAtPrev = test.runnableAtPrev;
if (item.runnableAtNext != null) {
item.runnableAtNext.runnableAtPrev = item;
}
if (item.runnableAtPrev != null) {
item.runnableAtPrev.runnableAtNext = item;
}
return (test == head) ? item : head;
}
tail = test;
test = test.runnableAtNext;
}
item.runnableAtPrev = tail;
item.runnableAtPrev.runnableAtNext = item;
return head;
}
/**
* Remove the given queue from a sorted linked list of "runnable" queues.
*
* @param head the current linked list head
* @param item the queue to remove
* @return a potentially updated linked list head
*/
@VisibleForTesting
static @Nullable BroadcastProcessQueue removeFromRunnableList(
@Nullable BroadcastProcessQueue head, @NonNull BroadcastProcessQueue item) {
if (head == item) {
head = item.runnableAtNext;
}
if (item.runnableAtNext != null) {
item.runnableAtNext.runnableAtPrev = item.runnableAtPrev;
}
if (item.runnableAtPrev != null) {
item.runnableAtPrev.runnableAtNext = item.runnableAtNext;
}
item.runnableAtNext = null;
item.runnableAtPrev = null;
return head;
}
/**
* Set the timeout flag to indicate that an ANR timer has been started. A value of true means a
* timer is running; a value of false means there is no timer running.
*/
void setTimeoutScheduled(boolean timeoutScheduled) {
mTimeoutScheduled = timeoutScheduled;
}
/**
* Get the timeout flag
*/
boolean timeoutScheduled() {
return mTimeoutScheduled;
}
@Override
public String toString() {
if (mCachedToString == null) {
mCachedToString = "BroadcastProcessQueue{" + toShortString() + "}";
}
return mCachedToString;
}
public String toShortString() {
if (mCachedToShortString == null) {
mCachedToShortString = Integer.toHexString(System.identityHashCode(this))
+ " " + ((app != null) ? app.getPid() : "?") + ":" + processName + "/"
+ UserHandle.formatUid(uid);
}
return mCachedToShortString;
}
public String describeStateLocked() {
return describeStateLocked(SystemClock.uptimeMillis());
}
public String describeStateLocked(@UptimeMillisLong long now) {
final StringBuilder sb = new StringBuilder();
if (isRunnable()) {
sb.append("runnable at ");
TimeUtils.formatDuration(getRunnableAt(), now, sb);
} else {
sb.append("not runnable");
}
sb.append(" because ");
sb.append(reasonToString(mRunnableAtReason));
return sb.toString();
}
@NeverCompile
public void dumpLocked(@UptimeMillisLong long now, @NonNull IndentingPrintWriter pw) {
if ((mActive == null) && isEmpty()) return;
pw.print(toShortString());
pw.print(" ");
pw.print(describeStateLocked(now));
pw.println();
pw.increaseIndent();
dumpProcessState(pw);
dumpBroadcastCounts(pw);
if (mActive != null) {
dumpRecord("ACTIVE", now, pw, mActive, mActiveIndex);
}
for (SomeArgs args : mPendingUrgent) {
final BroadcastRecord r = (BroadcastRecord) args.arg1;
dumpRecord("URGENT", now, pw, r, args.argi1);
}
for (SomeArgs args : mPending) {
final BroadcastRecord r = (BroadcastRecord) args.arg1;
dumpRecord(null, now, pw, r, args.argi1);
}
for (SomeArgs args : mPendingOffload) {
final BroadcastRecord r = (BroadcastRecord) args.arg1;
dumpRecord("OFFLOAD", now, pw, r, args.argi1);
}
pw.decreaseIndent();
pw.println();
}
@NeverCompile
private void dumpProcessState(@NonNull IndentingPrintWriter pw) {
final StringBuilder sb = new StringBuilder();
if (mUidForeground) {
sb.append("FG");
}
if (mProcessFreezable) {
if (sb.length() > 0) sb.append("|");
sb.append("FRZ");
}
if (mProcessInstrumented) {
if (sb.length() > 0) sb.append("|");
sb.append("INSTR");
}
if (mProcessPersistent) {
if (sb.length() > 0) sb.append("|");
sb.append("PER");
}
if (sb.length() > 0) {
pw.print("state:"); pw.println(sb);
}
if (runningOomAdjusted) {
pw.print("runningOomAdjusted:"); pw.println(runningOomAdjusted);
}
}
@NeverCompile
private void dumpBroadcastCounts(@NonNull IndentingPrintWriter pw) {
pw.print("e:"); pw.print(mCountEnqueued);
pw.print(" d:"); pw.print(mCountDeferred);
pw.print(" f:"); pw.print(mCountForeground);
pw.print(" fd:"); pw.print(mCountForegroundDeferred);
pw.print(" o:"); pw.print(mCountOrdered);
pw.print(" a:"); pw.print(mCountAlarm);
pw.print(" p:"); pw.print(mCountPrioritized);
pw.print(" pd:"); pw.print(mCountPrioritizedDeferred);
pw.print(" int:"); pw.print(mCountInteractive);
pw.print(" rt:"); pw.print(mCountResultTo);
pw.print(" ins:"); pw.print(mCountInstrumented);
pw.print(" m:"); pw.print(mCountManifest);
pw.print(" csi:"); pw.print(mActiveCountSinceIdle);
pw.print(" adcsi:"); pw.print(mActiveAssumedDeliveryCountSinceIdle);
pw.print(" ccu:"); pw.print(mActiveCountConsecutiveUrgent);
pw.print(" ccn:"); pw.print(mActiveCountConsecutiveNormal);
pw.println();
}
@NeverCompile
private void dumpRecord(@Nullable String flavor, @UptimeMillisLong long now,
@NonNull IndentingPrintWriter pw, @NonNull BroadcastRecord record, int recordIndex) {
TimeUtils.formatDuration(record.enqueueTime, now, pw);
pw.print(' ');
pw.println(record.toShortString());
pw.print(" ");
final int deliveryState = record.delivery[recordIndex];
pw.print(deliveryStateToString(deliveryState));
if (deliveryState == BroadcastRecord.DELIVERY_SCHEDULED) {
pw.print(" at ");
TimeUtils.formatDuration(record.scheduledTime[recordIndex], now, pw);
}
if (flavor != null) {
pw.print(' ');
pw.print(flavor);
}
final Object receiver = record.receivers.get(recordIndex);
if (receiver instanceof BroadcastFilter) {
final BroadcastFilter filter = (BroadcastFilter) receiver;
pw.print(" for registered ");
pw.print(Integer.toHexString(System.identityHashCode(filter)));
} else /* if (receiver instanceof ResolveInfo) */ {
final ResolveInfo info = (ResolveInfo) receiver;
pw.print(" for manifest ");
pw.print(info.activityInfo.name);
}
pw.println();
final int blockedUntilBeyondCount = record.blockedUntilBeyondCount[recordIndex];
if (blockedUntilBeyondCount != -1) {
pw.print(" blocked until ");
pw.print(blockedUntilBeyondCount);
pw.print(", currently at ");
pw.print(record.beyondCount);
pw.print(" of ");
pw.println(record.receivers.size());
}
}
}