blob: a36a9f64bded8f046db470431270acb12fca106d [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 android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_UNKNOWN;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
import static com.android.server.am.BroadcastProcessQueue.insertIntoRunnableList;
import static com.android.server.am.BroadcastProcessQueue.removeFromRunnableList;
import static com.android.server.am.BroadcastRecord.deliveryStateToString;
import static com.android.server.am.BroadcastRecord.getReceiverPackageName;
import static com.android.server.am.BroadcastRecord.getReceiverProcessName;
import static com.android.server.am.BroadcastRecord.getReceiverUid;
import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal;
import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER;
import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.IApplicationThread;
import android.app.RemoteServiceException.CannotDeliverBroadcastException;
import android.app.UidObserver;
import android.app.usage.UsageEvents.Event;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import com.android.internal.os.TimeoutRecord;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.am.BroadcastProcessQueue.BroadcastConsumer;
import com.android.server.am.BroadcastProcessQueue.BroadcastPredicate;
import com.android.server.am.BroadcastRecord.DeliveryState;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.function.Predicate;
/**
* Alternative {@link BroadcastQueue} implementation which pivots broadcasts to
* be dispatched on a per-process basis.
* <p>
* Each process now has its own broadcast queue represented by a
* {@link BroadcastProcessQueue} instance. 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>
* To keep things easy to reason about, there is a <em>very strong</em>
* preference to have broadcast interactions flow through a consistent set of
* methods in this specific order:
* <ol>
* <li>{@link #updateRunnableList} promotes a per-process queue to be runnable
* when it has relevant pending broadcasts
* <li>{@link #updateRunningList} promotes a runnable queue to be running and
* schedules delivery of the first broadcast
* <li>{@link #scheduleReceiverColdLocked} requests any needed cold-starts, and
* results are reported back via {@link #onApplicationAttachedLocked}
* <li>{@link #scheduleReceiverWarmLocked} requests dispatch of the currently
* active broadcast to a running app, and results are reported back via
* {@link #finishReceiverLocked}
* </ol>
*/
class BroadcastQueueModernImpl extends BroadcastQueue {
BroadcastQueueModernImpl(ActivityManagerService service, Handler handler,
BroadcastConstants fgConstants, BroadcastConstants bgConstants) {
this(service, handler, fgConstants, bgConstants, new BroadcastSkipPolicy(service),
new BroadcastHistory());
}
BroadcastQueueModernImpl(ActivityManagerService service, Handler handler,
BroadcastConstants fgConstants, BroadcastConstants bgConstants,
BroadcastSkipPolicy skipPolicy, BroadcastHistory history) {
super(service, handler, "modern", skipPolicy, history);
// For the moment, read agnostic constants from foreground
mConstants = Objects.requireNonNull(fgConstants);
mFgConstants = Objects.requireNonNull(fgConstants);
mBgConstants = Objects.requireNonNull(bgConstants);
mLocalHandler = new Handler(handler.getLooper(), mLocalCallback);
// We configure runnable size only once at boot; it'd be too complex to
// try resizing dynamically at runtime
mRunning = new BroadcastProcessQueue[mConstants.MAX_RUNNING_PROCESS_QUEUES];
}
// TODO: add support for replacing pending broadcasts
// TODO: add support for merging pending broadcasts
// TODO: consider reordering foreground broadcasts within queue
// TODO: pause queues when background services are running
// TODO: pause queues when processes are frozen
/**
* Map from UID to per-process broadcast queues. If a UID hosts more than
* one process, each additional process is stored as a linked list using
* {@link BroadcastProcessQueue#next}.
*
* @see #getProcessQueue
* @see #getOrCreateProcessQueue
*/
@GuardedBy("mService")
private final SparseArray<BroadcastProcessQueue> mProcessQueues = new SparseArray<>();
/**
* Head of linked list containing queues which are "runnable". They're
* sorted by {@link BroadcastProcessQueue#getRunnableAt()} so that we prefer
* dispatching of longer-waiting broadcasts first.
*
* @see BroadcastProcessQueue#insertIntoRunnableList
* @see BroadcastProcessQueue#removeFromRunnableList
*/
private BroadcastProcessQueue mRunnableHead = null;
/**
* Array of queues which are currently "running", which may have gaps that
* are {@code null}.
*
* @see #getRunningSize
* @see #getRunningIndexOf
*/
@GuardedBy("mService")
private final BroadcastProcessQueue[] mRunning;
/**
* Single queue which is "running" but is awaiting a cold start to be
* completed via {@link #onApplicationAttachedLocked}. To optimize for
* system health we only request one cold start at a time.
*/
@GuardedBy("mService")
private @Nullable BroadcastProcessQueue mRunningColdStart;
/**
* Collection of latches waiting for queue to go idle.
*/
@GuardedBy("mService")
private final ArrayList<CountDownLatch> mWaitingForIdle = new ArrayList<>();
private final BroadcastConstants mConstants;
private final BroadcastConstants mFgConstants;
private final BroadcastConstants mBgConstants;
private static final int MSG_UPDATE_RUNNING_LIST = 1;
private static final int MSG_DELIVERY_TIMEOUT = 2;
private static final int MSG_BG_ACTIVITY_START_TIMEOUT = 3;
private void enqueueUpdateRunningList() {
mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);
mLocalHandler.sendEmptyMessage(MSG_UPDATE_RUNNING_LIST);
}
private final Handler mLocalHandler;
private final Handler.Callback mLocalCallback = (msg) -> {
switch (msg.what) {
case MSG_UPDATE_RUNNING_LIST: {
synchronized (mService) {
updateRunningList();
}
return true;
}
case MSG_DELIVERY_TIMEOUT: {
synchronized (mService) {
finishReceiverLocked((BroadcastProcessQueue) msg.obj,
BroadcastRecord.DELIVERY_TIMEOUT);
}
return true;
}
case MSG_BG_ACTIVITY_START_TIMEOUT: {
synchronized (mService) {
final SomeArgs args = (SomeArgs) msg.obj;
final ProcessRecord app = (ProcessRecord) args.arg1;
final BroadcastRecord r = (BroadcastRecord) args.arg2;
args.recycle();
app.removeAllowBackgroundActivityStartsToken(r);
}
return true;
}
}
return false;
};
/**
* Return the total number of active queues contained inside
* {@link #mRunning}.
*/
private int getRunningSize() {
int size = 0;
for (int i = 0; i < mRunning.length; i++) {
if (mRunning[i] != null) size++;
}
return size;
}
/**
* Return the first index of the given value contained inside
* {@link #mRunning}, otherwise {@code -1}.
*/
private int getRunningIndexOf(@Nullable BroadcastProcessQueue test) {
for (int i = 0; i < mRunning.length; i++) {
if (mRunning[i] == test) return i;
}
return -1;
}
/**
* Consider updating the list of "runnable" queues, specifically with
* relation to the given queue.
* <p>
* Typically called when {@link BroadcastProcessQueue#getRunnableAt()} might
* have changed, since that influences the order in which we'll promote a
* "runnable" queue to be "running."
*/
@GuardedBy("mService")
private void updateRunnableList(@NonNull BroadcastProcessQueue queue) {
if (getRunningIndexOf(queue) >= 0) {
// Already running; they'll be reinserted into the runnable list
// once they finish running, so no need to update them now
return;
}
final boolean wantQueue = queue.isRunnable();
final boolean inQueue = (queue == mRunnableHead) || (queue.runnableAtPrev != null)
|| (queue.runnableAtNext != null);
if (wantQueue) {
if (inQueue) {
// We're in a good state, but our position within the linked
// list might need to move based on a runnableAt change
final boolean prevLower = (queue.runnableAtPrev != null)
? queue.runnableAtPrev.getRunnableAt() <= queue.getRunnableAt() : true;
final boolean nextHigher = (queue.runnableAtNext != null)
? queue.runnableAtNext.getRunnableAt() >= queue.getRunnableAt() : true;
if (!prevLower || !nextHigher) {
mRunnableHead = removeFromRunnableList(mRunnableHead, queue);
mRunnableHead = insertIntoRunnableList(mRunnableHead, queue);
}
} else {
mRunnableHead = insertIntoRunnableList(mRunnableHead, queue);
}
} else if (inQueue) {
mRunnableHead = removeFromRunnableList(mRunnableHead, queue);
}
// If app isn't running, and there's nothing in the queue, clean up
if (queue.isEmpty() && !queue.isProcessWarm()) {
removeProcessQueue(queue.processName, queue.uid);
}
}
/**
* Consider updating the list of "running" queues.
* <p>
* This method can promote "runnable" queues to become "running", subject to
* a maximum of {@link BroadcastConstants#MAX_RUNNING_PROCESS_QUEUES} warm
* processes and only one pending cold-start.
*/
@GuardedBy("mService")
private void updateRunningList() {
int avail = mRunning.length - getRunningSize();
if (avail == 0) return;
final int cookie = traceBegin(TAG, "updateRunningList");
final long now = SystemClock.uptimeMillis();
// If someone is waiting to go idle, everything is runnable now
final boolean waitingForIdle = !mWaitingForIdle.isEmpty();
// We're doing an update now, so remove any future update requests;
// we'll repost below if needed
mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);
boolean updateOomAdj = false;
BroadcastProcessQueue queue = mRunnableHead;
while (queue != null && avail > 0) {
BroadcastProcessQueue nextQueue = queue.runnableAtNext;
final long runnableAt = queue.getRunnableAt();
// If queues beyond this point aren't ready to run yet, schedule
// another pass when they'll be runnable
if (runnableAt > now && !waitingForIdle) {
mLocalHandler.sendEmptyMessageAtTime(MSG_UPDATE_RUNNING_LIST, runnableAt);
break;
}
// We might not have heard about a newly running process yet, so
// consider refreshing if we think we're cold
updateWarmProcess(queue);
final boolean processWarm = queue.isProcessWarm();
if (!processWarm) {
// We only offer to run one cold-start at a time to preserve
// system resources; below we either claim that single slot or
// skip to look for another warm process
if (mRunningColdStart == null) {
mRunningColdStart = queue;
} else {
// Move to considering next runnable queue
queue = nextQueue;
continue;
}
}
if (DEBUG_BROADCAST) logv("Promoting " + queue
+ " from runnable to running; process is " + queue.app);
// Allocate this available permit and start running!
final int queueIndex = getRunningIndexOf(null);
mRunning[queueIndex] = queue;
avail--;
// Remove ourselves from linked list of runnable things
mRunnableHead = removeFromRunnableList(mRunnableHead, queue);
// Emit all trace events for this process into a consistent track
queue.traceTrackName = TAG + ".mRunning[" + queueIndex + "]";
// If we're already warm, boost OOM adjust now; if cold we'll boost
// it after the app has been started
if (processWarm) {
notifyStartedRunning(queue);
}
// If we're already warm, schedule next pending broadcast now;
// otherwise we'll wait for the cold start to circle back around
queue.makeActiveNextPending();
if (processWarm) {
queue.traceProcessRunningBegin();
scheduleReceiverWarmLocked(queue);
} else {
queue.traceProcessStartingBegin();
scheduleReceiverColdLocked(queue);
}
// We've moved at least one process into running state above, so we
// need to kick off an OOM adjustment pass
updateOomAdj = true;
// Move to considering next runnable queue
queue = nextQueue;
}
if (updateOomAdj) {
mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER);
}
if (waitingForIdle && isIdleLocked()) {
mWaitingForIdle.forEach((latch) -> latch.countDown());
mWaitingForIdle.clear();
}
traceEnd(TAG, cookie);
}
@Override
public boolean onApplicationAttachedLocked(@NonNull ProcessRecord app) {
boolean didSomething = false;
if ((mRunningColdStart != null) && (mRunningColdStart.app == app)) {
// We've been waiting for this app to cold start, and it's ready
// now; dispatch its next broadcast and clear the slot
final BroadcastProcessQueue queue = mRunningColdStart;
mRunningColdStart = null;
queue.traceProcessEnd();
queue.traceProcessRunningBegin();
scheduleReceiverWarmLocked(queue);
// We might be willing to kick off another cold start
enqueueUpdateRunningList();
didSomething = true;
}
return didSomething;
}
@Override
public boolean onApplicationTimeoutLocked(@NonNull ProcessRecord app) {
return onApplicationCleanupLocked(app);
}
@Override
public boolean onApplicationProblemLocked(@NonNull ProcessRecord app) {
return onApplicationCleanupLocked(app);
}
@Override
public boolean onApplicationCleanupLocked(@NonNull ProcessRecord app) {
boolean didSomething = false;
if ((mRunningColdStart != null) && (mRunningColdStart.app == app)) {
// We've been waiting for this app to cold start, and it had
// trouble; clear the slot and fail delivery below
mRunningColdStart = null;
// We might be willing to kick off another cold start
enqueueUpdateRunningList();
didSomething = true;
}
final BroadcastProcessQueue queue = getProcessQueue(app);
if (queue != null) {
queue.app = null;
// If queue was running a broadcast, fail it
if (queue.isActive()) {
finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
didSomething = true;
}
// If queue has nothing else pending, consider cleaning it
if (queue.isEmpty()) {
updateRunnableList(queue);
}
}
return didSomething;
}
@Override
public int getPreferredSchedulingGroupLocked(@NonNull ProcessRecord app) {
final BroadcastProcessQueue queue = getProcessQueue(app);
if ((queue != null) && getRunningIndexOf(queue) >= 0) {
return queue.getPreferredSchedulingGroupLocked();
}
return ProcessList.SCHED_GROUP_UNDEFINED;
}
@Override
public void enqueueBroadcastLocked(@NonNull BroadcastRecord r) {
// TODO: handle empty receivers to deliver result immediately
if (r.receivers == null) return;
final IntentFilter removeMatchingFilter = (r.options != null)
? r.options.getRemoveMatchingFilter() : null;
if (removeMatchingFilter != null) {
final Predicate<Intent> removeMatching = removeMatchingFilter.asPredicate();
skipMatchingBroadcasts(QUEUE_PREDICATE_ANY, (testRecord, testReceiver) -> {
// We only allow caller to clear broadcasts they enqueued
return (testRecord.callingUid == r.callingUid)
&& removeMatching.test(testRecord.intent);
});
}
r.enqueueTime = SystemClock.uptimeMillis();
r.enqueueRealTime = SystemClock.elapsedRealtime();
r.enqueueClockTime = System.currentTimeMillis();
for (int i = 0; i < r.receivers.size(); i++) {
final Object receiver = r.receivers.get(i);
final BroadcastProcessQueue queue = getOrCreateProcessQueue(
getReceiverProcessName(receiver), getReceiverUid(receiver));
queue.enqueueBroadcast(r, i);
updateRunnableList(queue);
enqueueUpdateRunningList();
}
}
/**
* 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
* through to {@link #scheduleReceiverWarmLocked} once it's ready.
*/
private void scheduleReceiverColdLocked(@NonNull BroadcastProcessQueue queue) {
checkState(queue.isActive(), "isActive");
// Remember that active broadcast was scheduled via a cold start
queue.setActiveViaColdStart(true);
final BroadcastRecord r = queue.getActive();
final int index = queue.getActiveIndex();
final Object receiver = r.receivers.get(index);
final ApplicationInfo info = ((ResolveInfo) receiver).activityInfo.applicationInfo;
final ComponentName component = ((ResolveInfo) receiver).activityInfo.getComponentName();
final int intentFlags = r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND;
final HostingRecord hostingRecord = new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST,
component, r.intent.getAction(), r.getHostingRecordTriggerType());
final boolean isActivityCapable = (r.options != null
&& r.options.getTemporaryAppAllowlistDuration() > 0);
final int zygotePolicyFlags = isActivityCapable ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE
: ZYGOTE_POLICY_FLAG_EMPTY;
final boolean allowWhileBooting = (r.intent.getFlags()
& Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0;
if (DEBUG_BROADCAST) logv("Scheduling " + r + " to cold " + queue);
queue.app = mService.startProcessLocked(queue.processName, info, true, intentFlags,
hostingRecord, zygotePolicyFlags, allowWhileBooting, false);
if (queue.app != null) {
notifyStartedRunning(queue);
} else {
mRunningColdStart = null;
finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
}
}
/**
* Schedule the currently active broadcast on the given queue when we know
* the process is warm.
* <p>
* There is a <em>very strong</em> preference to consistently handle all
* results by calling through to {@link #finishReceiverLocked}, both in the
* case where a broadcast is handled by a remote app, and the case where the
* broadcast was finished locally without the remote app being involved.
*/
private void scheduleReceiverWarmLocked(@NonNull BroadcastProcessQueue queue) {
checkState(queue.isActive(), "isActive");
final ProcessRecord app = queue.app;
final BroadcastRecord r = queue.getActive();
final int index = queue.getActiveIndex();
final Object receiver = r.receivers.get(index);
// If someone already finished this broadcast, finish immediately
final int oldDeliveryState = getDeliveryState(r, index);
if (isDeliveryStateTerminal(oldDeliveryState)) {
finishReceiverLocked(queue, oldDeliveryState);
return;
}
// Consider additional cases where we'd want fo finish immediately
if (app.isInFullBackup()) {
finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
return;
}
if (mSkipPolicy.shouldSkip(r, receiver)) {
finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
return;
}
final Intent receiverIntent = r.getReceiverIntent(receiver);
if (receiverIntent == null) {
finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
return;
}
if (!r.timeoutExempt) {
final long timeout = r.isForeground() ? mFgConstants.TIMEOUT : mBgConstants.TIMEOUT;
mLocalHandler.sendMessageDelayed(
Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT, queue), timeout);
}
if (r.allowBackgroundActivityStarts) {
app.addOrUpdateAllowBackgroundActivityStartsToken(r, r.mBackgroundActivityStartsToken);
final long timeout = r.isForeground() ? mFgConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT
: mBgConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT;
final SomeArgs args = SomeArgs.obtain();
args.arg1 = app;
args.arg2 = r;
mLocalHandler.sendMessageDelayed(
Message.obtain(mLocalHandler, MSG_BG_ACTIVITY_START_TIMEOUT, args), timeout);
}
if (r.options != null && r.options.getTemporaryAppAllowlistDuration() > 0) {
mService.tempAllowlistUidLocked(queue.uid,
r.options.getTemporaryAppAllowlistDuration(),
r.options.getTemporaryAppAllowlistReasonCode(), r.toShortString(),
r.options.getTemporaryAppAllowlistType(), r.callingUid);
}
if (DEBUG_BROADCAST) logv("Scheduling " + r + " to warm " + app);
setDeliveryState(queue, r, index, receiver, BroadcastRecord.DELIVERY_SCHEDULED);
final IApplicationThread thread = app.getThread();
if (thread != null) {
try {
if (receiver instanceof BroadcastFilter) {
notifyScheduleRegisteredReceiver(app, r, (BroadcastFilter) receiver);
thread.scheduleRegisteredReceiver(
((BroadcastFilter) receiver).receiverList.receiver, receiverIntent,
r.resultCode, r.resultData, r.resultExtras, r.ordered, r.initialSticky,
r.userId, app.mState.getReportedProcState());
// TODO: consider making registered receivers of unordered
// broadcasts report results to detect ANRs
if (!r.ordered) {
finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED);
}
} else {
notifyScheduleReceiver(app, r, (ResolveInfo) receiver);
thread.scheduleReceiver(receiverIntent, ((ResolveInfo) receiver).activityInfo,
null, r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
app.mState.getReportedProcState());
}
} catch (RemoteException e) {
finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
app.scheduleCrashLocked(TAG, CannotDeliverBroadcastException.TYPE_ID, null);
}
} else {
finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
}
}
/**
* Schedule the final {@link BroadcastRecord#resultTo} delivery for an
* ordered broadcast; assumes the sender is still a warm process.
*/
private void scheduleResultTo(@NonNull BroadcastRecord r) {
if ((r.callerApp == null) || (r.resultTo == null)) return;
final ProcessRecord app = r.callerApp;
final IApplicationThread thread = app.getThread();
if (thread != null) {
mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(
app, OOM_ADJ_REASON_FINISH_RECEIVER);
try {
thread.scheduleRegisteredReceiver(r.resultTo, r.intent,
r.resultCode, r.resultData, r.resultExtras, false, r.initialSticky,
r.userId, app.mState.getReportedProcState());
} catch (RemoteException e) {
app.scheduleCrashLocked(TAG, CannotDeliverBroadcastException.TYPE_ID, null);
}
}
}
@Override
public boolean finishReceiverLocked(@NonNull ProcessRecord app, int resultCode,
@Nullable String resultData, @Nullable Bundle resultExtras, boolean resultAbort,
boolean waitForServices) {
final BroadcastProcessQueue queue = getProcessQueue(app);
final BroadcastRecord r = queue.getActive();
r.resultCode = resultCode;
r.resultData = resultData;
r.resultExtras = resultExtras;
if (!r.isNoAbort()) {
r.resultAbort = resultAbort;
}
// When the caller aborted an ordered broadcast, we mark all remaining
// receivers as skipped
if (r.ordered && r.resultAbort) {
for (int i = r.finishedCount + 1; i < r.receivers.size(); i++) {
setDeliveryState(null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED);
}
}
return finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED);
}
private boolean finishReceiverLocked(@NonNull BroadcastProcessQueue queue,
@DeliveryState int deliveryState) {
checkState(queue.isActive(), "isActive");
final ProcessRecord app = queue.app;
final BroadcastRecord r = queue.getActive();
final int index = queue.getActiveIndex();
final Object receiver = r.receivers.get(index);
setDeliveryState(queue, r, index, receiver, deliveryState);
if (deliveryState == BroadcastRecord.DELIVERY_TIMEOUT) {
if (app != null && !app.isDebugging()) {
mService.appNotResponding(queue.app, TimeoutRecord
.forBroadcastReceiver("Broadcast of " + r.toShortString()));
}
} else {
mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT, queue);
}
// Even if we have more broadcasts, if we've made reasonable progress
// and someone else is waiting, retire ourselves to avoid starvation
final boolean shouldRetire = (mRunnableHead != null)
&& (queue.getActiveCountSinceIdle() > mConstants.MAX_RUNNING_ACTIVE_BROADCASTS);
if (queue.isRunnable() && queue.isProcessWarm() && !shouldRetire) {
// We're on a roll; move onto the next broadcast for this process
queue.makeActiveNextPending();
scheduleReceiverWarmLocked(queue);
return true;
} else {
// We've drained running broadcasts; maybe move back to runnable
queue.makeActiveIdle();
queue.traceProcessEnd();
final int queueIndex = getRunningIndexOf(queue);
mRunning[queueIndex] = null;
updateRunnableList(queue);
enqueueUpdateRunningList();
// Tell other OS components that app is not actively running, giving
// a chance to update OOM adjustment
notifyStoppedRunning(queue);
return false;
}
}
/**
* Set the delivery state on the given broadcast, then apply any additional
* bookkeeping related to ordered broadcasts.
*/
private void setDeliveryState(@Nullable BroadcastProcessQueue queue,
@NonNull BroadcastRecord r, int index, @NonNull Object receiver,
@DeliveryState int newDeliveryState) {
final int oldDeliveryState = getDeliveryState(r, index);
if (newDeliveryState != BroadcastRecord.DELIVERY_DELIVERED) {
Slog.w(TAG, "Delivery state of " + r + " to " + receiver + " changed from "
+ deliveryStateToString(oldDeliveryState) + " to "
+ deliveryStateToString(newDeliveryState));
}
r.setDeliveryState(index, newDeliveryState);
// Emit any relevant tracing results when we're changing the delivery
// state as part of running from a queue
if (queue != null) {
if (newDeliveryState == BroadcastRecord.DELIVERY_SCHEDULED) {
queue.traceActiveBegin();
} else if ((oldDeliveryState == BroadcastRecord.DELIVERY_SCHEDULED)
&& isDeliveryStateTerminal(newDeliveryState)) {
queue.traceActiveEnd();
}
}
// If we're moving into a terminal state, we might have internal
// bookkeeping to update for ordered broadcasts
if (!isDeliveryStateTerminal(oldDeliveryState)
&& isDeliveryStateTerminal(newDeliveryState)) {
r.finishedCount++;
notifyFinishReceiver(queue, r, index, receiver);
if (r.ordered) {
if (r.finishedCount < r.receivers.size()) {
// We just finished an ordered receiver, which means the
// next receiver might now be runnable
final Object nextReceiver = r.receivers.get(r.finishedCount);
final BroadcastProcessQueue nextQueue = getProcessQueue(
getReceiverProcessName(nextReceiver), getReceiverUid(nextReceiver));
nextQueue.invalidateRunnableAt();
updateRunnableList(nextQueue);
} else {
// Everything finished, so deliver final result
scheduleResultTo(r);
}
}
}
}
private @DeliveryState int getDeliveryState(@NonNull BroadcastRecord r, int index) {
return r.getDeliveryState(index);
}
@Override
public boolean cleanupDisabledPackageReceiversLocked(@Nullable String packageName,
@Nullable Set<String> filterByClasses, int userId) {
final Predicate<BroadcastProcessQueue> queuePredicate;
final BroadcastPredicate broadcastPredicate;
if (packageName != null) {
// Caller provided a package and user ID, so we're focused on queues
// belonging to a specific UID
final int uid = mService.mPackageManagerInt.getPackageUid(
packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
queuePredicate = (q) -> {
return q.uid == uid;
};
// If caller provided a set of classes, filter to skip only those;
// otherwise we skip all broadcasts
if (filterByClasses != null) {
broadcastPredicate = (r, i) -> {
final Object receiver = r.receivers.get(i);
if (receiver instanceof ResolveInfo) {
final ActivityInfo info = ((ResolveInfo) receiver).activityInfo;
return packageName.equals(info.packageName)
&& filterByClasses.contains(info.name);
} else {
return false;
}
};
} else {
broadcastPredicate = (r, i) -> {
final Object receiver = r.receivers.get(i);
return packageName.equals(getReceiverPackageName(receiver));
};
}
} else {
// Caller is cleaning up an entire user ID; skip all broadcasts
queuePredicate = (q) -> {
return UserHandle.getUserId(q.uid) == userId;
};
broadcastPredicate = BROADCAST_PREDICATE_ANY;
}
return skipMatchingBroadcasts(queuePredicate, broadcastPredicate);
}
private static final Predicate<BroadcastProcessQueue> QUEUE_PREDICATE_ANY =
(q) -> true;
private static final BroadcastPredicate BROADCAST_PREDICATE_ANY =
(r, i) -> true;
/**
* Typical consumer that will skip the given broadcast, usually as a result
* of it matching a predicate.
*/
private final BroadcastConsumer mBroadcastConsumerSkip = (r, i) -> {
setDeliveryState(null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED);
};
private boolean skipMatchingBroadcasts(
@NonNull Predicate<BroadcastProcessQueue> queuePredicate,
@NonNull BroadcastPredicate broadcastPredicate) {
// Note that we carefully preserve any "skipped" broadcasts in their
// queues so that we follow our normal flow for "finishing" a broadcast,
// which is where we handle things like ordered broadcasts.
boolean didSomething = false;
for (int i = 0; i < mProcessQueues.size(); i++) {
BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
while (leaf != null) {
if (queuePredicate.test(leaf)) {
didSomething |= leaf.removeMatchingBroadcasts(broadcastPredicate,
mBroadcastConsumerSkip);
}
leaf = leaf.processNameNext;
}
}
return didSomething;
}
@Override
public void start(@NonNull ContentResolver resolver) {
mFgConstants.startObserving(mHandler, resolver);
mBgConstants.startObserving(mHandler, resolver);
mService.registerUidObserver(new UidObserver() {
@Override
public void onUidCachedChanged(int uid, boolean cached) {
synchronized (mService) {
BroadcastProcessQueue leaf = mProcessQueues.get(uid);
while (leaf != null) {
leaf.setProcessCached(cached);
updateRunnableList(leaf);
leaf = leaf.processNameNext;
}
enqueueUpdateRunningList();
}
}
}, ActivityManager.UID_OBSERVER_CACHED, 0, "android");
}
@Override
public boolean isIdleLocked() {
return (mRunnableHead == null) && (getRunningSize() == 0);
}
@Override
public void waitForIdle(@Nullable PrintWriter pw) {
final CountDownLatch latch = new CountDownLatch(1);
synchronized (mService) {
mWaitingForIdle.add(latch);
}
enqueueUpdateRunningList();
try {
latch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Override
public void waitForBarrier(@Nullable PrintWriter pw) {
// TODO: implement
throw new UnsupportedOperationException();
}
@Override
public String describeStateLocked() {
return getRunningSize() + " running";
}
@Override
public boolean isDelayBehindServices() {
// TODO: implement
return false;
}
@Override
public void backgroundServicesFinishedLocked(int userId) {
// TODO: implement
}
private int traceBegin(String trackName, String methodName) {
final int cookie = methodName.hashCode();
Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
trackName, methodName, cookie);
return cookie;
}
private void traceEnd(String trackName, int cookie) {
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
trackName, cookie);
}
private void updateWarmProcess(@NonNull BroadcastProcessQueue queue) {
if (!queue.isProcessWarm()) {
queue.app = mService.getProcessRecordLocked(queue.processName, queue.uid);
}
}
/**
* Inform other parts of OS that the given broadcast queue has started
* running, typically for internal bookkeeping.
*/
private void notifyStartedRunning(@NonNull BroadcastProcessQueue queue) {
if (queue.app != null) {
queue.app.mReceivers.incrementCurReceivers();
queue.app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
// Don't bump its LRU position if it's in the background restricted.
if (mService.mInternal.getRestrictionLevel(
queue.uid) < ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
mService.updateLruProcessLocked(queue.app, false, null);
}
mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(queue.app,
OOM_ADJ_REASON_START_RECEIVER);
mService.enqueueOomAdjTargetLocked(queue.app);
}
}
/**
* Inform other parts of OS that the given broadcast queue has stopped
* running, typically for internal bookkeeping.
*/
private void notifyStoppedRunning(@NonNull BroadcastProcessQueue queue) {
if (queue.app != null) {
// Update during our next pass; no need for an immediate update
mService.enqueueOomAdjTargetLocked(queue.app);
queue.app.mReceivers.decrementCurReceivers();
}
}
/**
* Inform other parts of OS that the given broadcast was just scheduled for
* a registered receiver, typically for internal bookkeeping.
*/
private void notifyScheduleRegisteredReceiver(@NonNull ProcessRecord app,
@NonNull BroadcastRecord r, @NonNull BroadcastFilter receiver) {
reportUsageStatsBroadcastDispatched(app, r);
}
/**
* Inform other parts of OS that the given broadcast was just scheduled for
* a manifest receiver, typically for internal bookkeeping.
*/
private void notifyScheduleReceiver(@NonNull ProcessRecord app,
@NonNull BroadcastRecord r, @NonNull ResolveInfo receiver) {
reportUsageStatsBroadcastDispatched(app, r);
final String receiverPackageName = receiver.activityInfo.packageName;
app.addPackage(receiverPackageName,
receiver.activityInfo.applicationInfo.longVersionCode, mService.mProcessStats);
final boolean targetedBroadcast = r.intent.getComponent() != null;
final boolean targetedSelf = Objects.equals(r.callerPackage, receiverPackageName);
if (targetedBroadcast && !targetedSelf) {
mService.mUsageStatsService.reportEvent(receiverPackageName,
r.userId, Event.APP_COMPONENT_USED);
}
mService.notifyPackageUse(receiverPackageName,
PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
mService.mPackageManagerInt.setPackageStoppedState(
receiverPackageName, false, r.userId);
}
private void reportUsageStatsBroadcastDispatched(@NonNull ProcessRecord app,
@NonNull BroadcastRecord r) {
final long idForResponseEvent = (r.options != null)
? r.options.getIdForResponseEvent() : 0L;
if (idForResponseEvent <= 0) return;
final String targetPackage;
if (r.intent.getPackage() != null) {
targetPackage = r.intent.getPackage();
} else if (r.intent.getComponent() != null) {
targetPackage = r.intent.getComponent().getPackageName();
} else {
targetPackage = null;
}
if (targetPackage == null) return;
mService.mUsageStatsService.reportBroadcastDispatched(r.callingUid, targetPackage,
UserHandle.of(r.userId), idForResponseEvent, SystemClock.elapsedRealtime(),
mService.getUidStateLocked(app.uid));
}
/**
* Inform other parts of OS that the given broadcast was just finished,
* typically for internal bookkeeping.
*/
private void notifyFinishReceiver(@Nullable BroadcastProcessQueue queue,
@NonNull BroadcastRecord r, int index, @NonNull Object receiver) {
// Report statistics for each individual receiver
final int uid = getReceiverUid(receiver);
final int senderUid = (r.callingUid == -1) ? Process.SYSTEM_UID : r.callingUid;
final String actionName = ActivityManagerService.getShortAction(r.intent.getAction());
final int receiverType = (receiver instanceof BroadcastFilter)
? BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME
: BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST;
final int type;
if (queue == null) {
type = BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_UNKNOWN;
} else if (queue.getActiveViaColdStart()) {
type = BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
} else {
type = BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
}
// With the new per-process queues, there's no delay between being
// "dispatched" and "scheduled", so we report no "receive delay"
final long dispatchDelay = r.scheduledTime[index] - r.enqueueTime;
final long receiveDelay = 0;
final long finishDelay = r.duration[index];
FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, uid, senderUid, actionName,
receiverType, type, dispatchDelay, receiveDelay, finishDelay);
final boolean recordFinished = (r.finishedCount == r.receivers.size());
if (recordFinished) {
mHistory.addBroadcastToHistoryLocked(r);
r.nextReceiver = r.receivers.size();
BroadcastQueueImpl.logBootCompletedBroadcastCompletionLatencyIfPossible(r);
if (r.intent.getComponent() == null && r.intent.getPackage() == null
&& (r.intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
int manifestCount = 0;
int manifestSkipCount = 0;
for (int i = 0; i < r.receivers.size(); i++) {
if (r.receivers.get(i) instanceof ResolveInfo) {
manifestCount++;
if (r.delivery[i] == BroadcastRecord.DELIVERY_SKIPPED) {
manifestSkipCount++;
}
}
}
final long dispatchTime = SystemClock.uptimeMillis() - r.enqueueTime;
mService.addBroadcastStatLocked(r.intent.getAction(), r.callerPackage,
manifestCount, manifestSkipCount, dispatchTime);
}
}
}
@VisibleForTesting
@NonNull BroadcastProcessQueue getOrCreateProcessQueue(@NonNull ProcessRecord app) {
return getOrCreateProcessQueue(app.processName, app.info.uid);
}
@VisibleForTesting
@NonNull BroadcastProcessQueue getOrCreateProcessQueue(@NonNull String processName,
int uid) {
BroadcastProcessQueue leaf = mProcessQueues.get(uid);
while (leaf != null) {
if (Objects.equals(leaf.processName, processName)) {
return leaf;
} else if (leaf.processNameNext == null) {
break;
}
leaf = leaf.processNameNext;
}
BroadcastProcessQueue created = new BroadcastProcessQueue(mConstants, processName, uid);
created.app = mService.getProcessRecordLocked(processName, uid);
if (leaf == null) {
mProcessQueues.put(uid, created);
} else {
leaf.processNameNext = created;
}
return created;
}
@VisibleForTesting
@Nullable BroadcastProcessQueue getProcessQueue(@NonNull ProcessRecord app) {
return getProcessQueue(app.processName, app.info.uid);
}
@VisibleForTesting
@Nullable BroadcastProcessQueue getProcessQueue(@NonNull String processName, int uid) {
BroadcastProcessQueue leaf = mProcessQueues.get(uid);
while (leaf != null) {
if (Objects.equals(leaf.processName, processName)) {
return leaf;
}
leaf = leaf.processNameNext;
}
return null;
}
@VisibleForTesting
@Nullable BroadcastProcessQueue removeProcessQueue(@NonNull ProcessRecord app) {
return removeProcessQueue(app.processName, app.info.uid);
}
@VisibleForTesting
@Nullable BroadcastProcessQueue removeProcessQueue(@NonNull String processName,
int uid) {
BroadcastProcessQueue prev = null;
BroadcastProcessQueue leaf = mProcessQueues.get(uid);
while (leaf != null) {
if (Objects.equals(leaf.processName, processName)) {
if (prev != null) {
prev.processNameNext = leaf.processNameNext;
} else {
if (leaf.processNameNext != null) {
mProcessQueues.put(uid, leaf.processNameNext);
} else {
mProcessQueues.remove(uid);
}
}
return leaf;
}
prev = leaf;
leaf = leaf.processNameNext;
}
return null;
}
@Override
public void dumpDebug(@NonNull ProtoOutputStream proto, long fieldId) {
long token = proto.start(fieldId);
proto.write(BroadcastQueueProto.QUEUE_NAME, mQueueName);
mHistory.dumpDebug(proto);
proto.end(token);
}
@Override
public boolean dumpLocked(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
@NonNull String[] args, int opti, boolean dumpAll, @Nullable String dumpPackage,
boolean needSep) {
final long now = SystemClock.uptimeMillis();
final IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
ipw.increaseIndent();
ipw.println();
ipw.println("📋 Per-process queues:");
ipw.increaseIndent();
for (int i = 0; i < mProcessQueues.size(); i++) {
BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
while (leaf != null) {
leaf.dumpLocked(ipw);
leaf = leaf.processNameNext;
}
}
ipw.decreaseIndent();
ipw.println();
ipw.println("🧍 Runnable:");
ipw.increaseIndent();
if (mRunnableHead == null) {
ipw.println("(none)");
} else {
BroadcastProcessQueue queue = mRunnableHead;
while (queue != null) {
TimeUtils.formatDuration(queue.getRunnableAt(), now, ipw);
ipw.print(' ');
ipw.println(queue.toShortString());
queue = queue.runnableAtNext;
}
}
ipw.decreaseIndent();
ipw.println();
ipw.println("🏃 Running:");
ipw.increaseIndent();
for (BroadcastProcessQueue queue : mRunning) {
if ((queue != null) && (queue == mRunningColdStart)) {
ipw.print("🥶 ");
} else {
ipw.print("\u3000 ");
}
if (queue != null) {
ipw.println(queue.toShortString());
} else {
ipw.println("(none)");
}
}
ipw.decreaseIndent();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
needSep = mHistory.dumpLocked(ipw, dumpPackage, mQueueName, sdf, dumpAll, needSep);
return needSep;
}
}