From 80566f8e0dc9a34a848189c8ee39a6c4f04b8d7b Mon Sep 17 00:00:00 2001 From: Sudheer Shanka Date: Mon, 1 May 2023 08:13:39 +0000 Subject: [PATCH] Increase the active broadcast delivery limit to core components. Also, update the logic to iteratively dispatch the broadcasts to avoid hitting stack overflow limits. Bug: 279116343 Bug: 274612112 Test: atest FrameworksMockingServicesTests:BroadcastQueueTest Test: atest FrameworksMockingServicesTests:BroadcastQueueModernImplTest Change-Id: I1a54b6ccc6d4c25b5aac7007d84c86a3aa0d736d --- .../android/server/am/BroadcastConstants.java | 40 ++- .../server/am/BroadcastProcessQueue.java | 18 ++ .../server/am/BroadcastQueueModernImpl.java | 263 +++++++++++------- .../android/server/am/BroadcastRecord.java | 8 + 4 files changed, 230 insertions(+), 99 deletions(-) diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java index 4d469639be6fa..87214decfe2eb 100644 --- a/services/core/java/com/android/server/am/BroadcastConstants.java +++ b/services/core/java/com/android/server/am/BroadcastConstants.java @@ -179,10 +179,38 @@ public class BroadcastConstants { * being "runnable" to give other processes a chance to run. */ public int MAX_RUNNING_ACTIVE_BROADCASTS = DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS; - private static final String KEY_MAX_RUNNING_ACTIVE_BROADCASTS = "bcast_max_running_active_broadcasts"; + private static final String KEY_MAX_RUNNING_ACTIVE_BROADCASTS = + "bcast_max_running_active_broadcasts"; private static final int DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS = ActivityManager.isLowRamDeviceStatic() ? 8 : 16; + /** + * For {@link BroadcastQueueModernImpl}: Maximum number of active "blocking" broadcasts + * to dispatch to a "running" System process queue before we retire them back to + * being "runnable" to give other processes a chance to run. Here "blocking" refers to + * whether or not we are going to block on the finishReceiver() to be called before moving + * to the next broadcast. + */ + public int MAX_CORE_RUNNING_BLOCKING_BROADCASTS = DEFAULT_MAX_CORE_RUNNING_BLOCKING_BROADCASTS; + private static final String KEY_CORE_MAX_RUNNING_BLOCKING_BROADCASTS = + "bcast_max_core_running_blocking_broadcasts"; + private static final int DEFAULT_MAX_CORE_RUNNING_BLOCKING_BROADCASTS = + ActivityManager.isLowRamDeviceStatic() ? 8 : 16; + + /** + * For {@link BroadcastQueueModernImpl}: Maximum number of active non-"blocking" broadcasts + * to dispatch to a "running" System process queue before we retire them back to + * being "runnable" to give other processes a chance to run. Here "blocking" refers to + * whether or not we are going to block on the finishReceiver() to be called before moving + * to the next broadcast. + */ + public int MAX_CORE_RUNNING_NON_BLOCKING_BROADCASTS = + DEFAULT_MAX_CORE_RUNNING_NON_BLOCKING_BROADCASTS; + private static final String KEY_CORE_MAX_RUNNING_NON_BLOCKING_BROADCASTS = + "bcast_max_core_running_non_blocking_broadcasts"; + private static final int DEFAULT_MAX_CORE_RUNNING_NON_BLOCKING_BROADCASTS = + ActivityManager.isLowRamDeviceStatic() ? 32 : 64; + /** * For {@link BroadcastQueueModernImpl}: Maximum number of pending * broadcasts to hold for a process before we ignore any delays that policy @@ -369,6 +397,12 @@ public class BroadcastConstants { DEFAULT_MAX_CONSECUTIVE_NORMAL_DISPATCHES); MAX_RUNNING_ACTIVE_BROADCASTS = getDeviceConfigInt(KEY_MAX_RUNNING_ACTIVE_BROADCASTS, DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS); + MAX_CORE_RUNNING_BLOCKING_BROADCASTS = getDeviceConfigInt( + KEY_CORE_MAX_RUNNING_BLOCKING_BROADCASTS, + DEFAULT_MAX_CORE_RUNNING_BLOCKING_BROADCASTS); + MAX_CORE_RUNNING_NON_BLOCKING_BROADCASTS = getDeviceConfigInt( + KEY_CORE_MAX_RUNNING_NON_BLOCKING_BROADCASTS, + DEFAULT_MAX_CORE_RUNNING_NON_BLOCKING_BROADCASTS); MAX_PENDING_BROADCASTS = getDeviceConfigInt(KEY_MAX_PENDING_BROADCASTS, DEFAULT_MAX_PENDING_BROADCASTS); DELAY_NORMAL_MILLIS = getDeviceConfigLong(KEY_DELAY_NORMAL_MILLIS, @@ -418,6 +452,10 @@ public class BroadcastConstants { pw.print(KEY_MODERN_QUEUE_ENABLED, MODERN_QUEUE_ENABLED).println(); pw.print(KEY_MAX_RUNNING_PROCESS_QUEUES, MAX_RUNNING_PROCESS_QUEUES).println(); pw.print(KEY_MAX_RUNNING_ACTIVE_BROADCASTS, MAX_RUNNING_ACTIVE_BROADCASTS).println(); + pw.print(KEY_CORE_MAX_RUNNING_BLOCKING_BROADCASTS, + MAX_CORE_RUNNING_BLOCKING_BROADCASTS).println(); + pw.print(KEY_CORE_MAX_RUNNING_NON_BLOCKING_BROADCASTS, + MAX_CORE_RUNNING_NON_BLOCKING_BROADCASTS).println(); pw.print(KEY_MAX_PENDING_BROADCASTS, MAX_PENDING_BROADCASTS).println(); pw.print(KEY_DELAY_NORMAL_MILLIS, TimeUtils.formatDuration(DELAY_NORMAL_MILLIS)).println(); diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index 5c68e67590833..7ced9db88071f 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -142,6 +142,12 @@ class BroadcastProcessQueue { */ 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. @@ -499,6 +505,14 @@ class BroadcastProcessQueue { 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; } @@ -532,6 +546,8 @@ class BroadcastProcessQueue { mActive = (BroadcastRecord) next.arg1; mActiveIndex = next.argi1; mActiveCountSinceIdle++; + mActiveAssumedDeliveryCountSinceIdle += + (mActive.isAssumedDelivered(mActiveIndex) ? 1 : 0); mActiveViaColdStart = false; mActiveWasStopped = false; next.recycle(); @@ -545,6 +561,7 @@ class BroadcastProcessQueue { mActive = null; mActiveIndex = 0; mActiveCountSinceIdle = 0; + mActiveAssumedDeliveryCountSinceIdle = 0; mActiveViaColdStart = false; invalidateRunnableAt(); } @@ -1309,6 +1326,7 @@ class BroadcastProcessQueue { 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(); diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index 96e152320282c..78ca2ba6bfce4 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -40,6 +40,7 @@ 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 android.annotation.CheckResult; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UptimeMillisLong; @@ -446,43 +447,29 @@ class BroadcastQueueModernImpl extends BroadcastQueue { 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.runningTraceTrackName = TAG + ".mRunning[" + queueIndex + "]"; - queue.runningOomAdjusted = queue.isPendingManifest() - || queue.isPendingOrdered() - || queue.isPendingResultTo(); - - // If already warm, we can make OOM adjust request immediately; - // otherwise we need to wait until process becomes warm + promoteToRunningLocked(queue); + final boolean completed; if (processWarm) { - notifyStartedRunning(queue); updateOomAdj |= queue.runningOomAdjusted; - } - - // 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); + completed = scheduleReceiverWarmLocked(queue); } else { - queue.traceProcessStartingBegin(); - scheduleReceiverColdLocked(queue); + completed = scheduleReceiverColdLocked(queue); } + // If we are done with delivering the broadcasts to the process, we can demote it + // from the "running" list. + if (completed) { + demoteFromRunningLocked(queue); + } + // TODO: If delivering broadcasts to a process is finished, we don't have to hold + // a slot for it. + avail--; // Move to considering next runnable queue queue = nextQueue; } + // TODO: We need to update oomAdj early as this currently doesn't guarantee that the + // procState is updated correctly when the app is handling a broadcast. if (updateOomAdj) { mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER); } @@ -514,7 +501,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue { queue.traceProcessEnd(); queue.traceProcessRunningBegin(); - scheduleReceiverWarmLocked(queue); + if (scheduleReceiverWarmLocked(queue)) { + demoteFromRunningLocked(queue); + } // We might be willing to kick off another cold start enqueueUpdateRunningList(); @@ -558,6 +547,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { if (queue.isActive()) { finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE, "onApplicationCleanupLocked"); + demoteFromRunningLocked(queue); } // Skip any pending registered receivers, since the old process @@ -695,8 +685,13 @@ class BroadcastQueueModernImpl extends BroadcastQueue { * 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. + * + * @return {@code true} if the broadcast delivery is finished and the process queue can + * be demoted from the running list. Otherwise {@code false}. */ - private void scheduleReceiverColdLocked(@NonNull BroadcastProcessQueue queue) { + @CheckResult + @GuardedBy("mService") + private boolean scheduleReceiverColdLocked(@NonNull BroadcastProcessQueue queue) { checkState(queue.isActive(), "isActive"); // Remember that active broadcast was scheduled via a cold start @@ -711,12 +706,14 @@ class BroadcastQueueModernImpl extends BroadcastQueue { mRunningColdStart = null; finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_SKIPPED, "BroadcastFilter for cold app"); - return; + return true; } - if (maybeSkipReceiver(queue, r, index)) { + final String skipReason = shouldSkipReceiver(queue, r, index); + if (skipReason != null) { mRunningColdStart = null; - return; + finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_SKIPPED, skipReason); + return true; } final ApplicationInfo info = ((ResolveInfo) receiver).activityInfo.applicationInfo; @@ -742,8 +739,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue { mRunningColdStart = null; finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE, "startProcessLocked failed"); - return; + return true; } + return false; } /** @@ -754,38 +752,46 @@ class BroadcastQueueModernImpl extends BroadcastQueue { * 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. + * + * @return {@code true} if the broadcast delivery is finished and the process queue can + * be demoted from the running list. Otherwise {@code false}. */ + @CheckResult @GuardedBy("mService") - private void scheduleReceiverWarmLocked(@NonNull BroadcastProcessQueue queue) { + private boolean scheduleReceiverWarmLocked(@NonNull BroadcastProcessQueue queue) { checkState(queue.isActive(), "isActive"); - final BroadcastRecord r = queue.getActive(); - final int index = queue.getActiveIndex(); + final int cookie = traceBegin("scheduleReceiverWarmLocked"); + while (queue.isActive()) { + final BroadcastRecord r = queue.getActive(); + final int index = queue.getActiveIndex(); - if (r.terminalCount == 0) { - r.dispatchTime = SystemClock.uptimeMillis(); - r.dispatchRealTime = SystemClock.elapsedRealtime(); - r.dispatchClockTime = System.currentTimeMillis(); - } + if (r.terminalCount == 0) { + r.dispatchTime = SystemClock.uptimeMillis(); + r.dispatchRealTime = SystemClock.elapsedRealtime(); + r.dispatchClockTime = System.currentTimeMillis(); + } - if (maybeSkipReceiver(queue, r, index)) { - return; - } - dispatchReceivers(queue, r, index); - } + final String skipReason = shouldSkipReceiver(queue, r, index); + if (skipReason == null) { + final boolean isBlockingDispatch = dispatchReceivers(queue, r, index); + if (isBlockingDispatch) { + traceEnd(cookie); + return false; + } + } else { + finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_SKIPPED, skipReason); + } - /** - * Examine a receiver and possibly skip it. The method returns true if the receiver is - * skipped (and therefore no more work is required). - */ - private boolean maybeSkipReceiver(@NonNull BroadcastProcessQueue queue, - @NonNull BroadcastRecord r, int index) { - final String reason = shouldSkipReceiver(queue, r, index); - if (reason != null) { - finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_SKIPPED, reason); - return true; + if (shouldRetire(queue)) { + break; + } + + // We're on a roll; move onto the next broadcast for this process + queue.makeActiveNextPending(); } - return false; + traceEnd(cookie); + return true; } /** @@ -825,25 +831,22 @@ class BroadcastQueueModernImpl extends BroadcastQueue { return null; } - /** - * Return true if this receiver should be assumed to have been delivered. - */ - private boolean isAssumedDelivered(BroadcastRecord r, int index) { - return (r.receivers.get(index) instanceof BroadcastFilter) && !r.ordered - && (r.resultTo == null); - } - /** * A receiver is about to be dispatched. Start ANR timers, if necessary. + * + * @return {@code true} if this a blocking delivery. That is, we are going to block on the + * finishReceiver() to be called before moving to the next broadcast. Otherwise, + * {@code false}. */ - private void dispatchReceivers(@NonNull BroadcastProcessQueue queue, + @CheckResult + private boolean dispatchReceivers(@NonNull BroadcastProcessQueue queue, @NonNull BroadcastRecord r, int index) { final ProcessRecord app = queue.app; final Object receiver = r.receivers.get(index); // Skip ANR tracking early during boot, when requested, or when we // immediately assume delivery success - final boolean assumeDelivered = isAssumedDelivered(r, index); + final boolean assumeDelivered = r.isAssumedDelivered(index); if (mService.mProcessesReady && !r.timeoutExempt && !assumeDelivered) { queue.lastCpuDelayTime = queue.app.getCpuDelayTime(); @@ -898,6 +901,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { if (assumeDelivered) { finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_DELIVERED, "assuming delivered"); + return false; } } else { notifyScheduleReceiver(app, r, (ResolveInfo) receiver); @@ -908,17 +912,21 @@ class BroadcastQueueModernImpl extends BroadcastQueue { r.shareIdentity ? r.callingUid : Process.INVALID_UID, r.shareIdentity ? r.callerPackage : null); } + return true; } catch (RemoteException e) { final String msg = "Failed to schedule " + r + " to " + receiver + " via " + app + ": " + e; logw(msg); app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER, ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true); - finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE, "remote app"); + finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE, + "remote app"); + return false; } } else { finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE, "missing IApplicationThread"); + return false; } } @@ -989,6 +997,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { private void deliveryTimeoutHardLocked(@NonNull BroadcastProcessQueue queue) { finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_TIMEOUT, "deliveryTimeoutHardLocked"); + demoteFromRunningLocked(queue); } @Override @@ -1015,7 +1024,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // To ensure that "beyond" high-water marks are updated in a monotonic // way, we finish this receiver before possibly skipping any remaining // aborted receivers - final boolean res = finishReceiverActiveLocked(queue, + finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_DELIVERED, "remote app"); // When the caller aborted an ordered broadcast, we mark all @@ -1027,30 +1036,52 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } } - return res; + if (shouldRetire(queue)) { + demoteFromRunningLocked(queue); + return true; + } + + // We're on a roll; move onto the next broadcast for this process + queue.makeActiveNextPending(); + if (scheduleReceiverWarmLocked(queue)) { + demoteFromRunningLocked(queue); + return true; + } + + return false; } /** - * Return true if there are more broadcasts in the queue and the queue is runnable. + * Return true if there are no more broadcasts in the queue or if the queue is not runnable. */ - private boolean shouldContinueScheduling(@NonNull BroadcastProcessQueue queue) { + private boolean shouldRetire(@NonNull BroadcastProcessQueue queue) { // If we've made reasonable progress, periodically retire ourselves to // avoid starvation of other processes and stack overflow when a // broadcast is immediately finished without waiting - final boolean shouldRetire = - (queue.getActiveCountSinceIdle() >= mConstants.MAX_RUNNING_ACTIVE_BROADCASTS); + final boolean shouldRetire; + if (UserHandle.isCore(queue.uid)) { + final int nonBlockingDeliveryCount = queue.getActiveAssumedDeliveryCountSinceIdle(); + final int blockingDeliveryCount = (queue.getActiveCountSinceIdle() + - queue.getActiveAssumedDeliveryCountSinceIdle()); + shouldRetire = (blockingDeliveryCount + >= mConstants.MAX_CORE_RUNNING_BLOCKING_BROADCASTS) || (nonBlockingDeliveryCount + >= mConstants.MAX_CORE_RUNNING_NON_BLOCKING_BROADCASTS); + } else { + shouldRetire = + (queue.getActiveCountSinceIdle() >= mConstants.MAX_RUNNING_ACTIVE_BROADCASTS); + } - return queue.isRunnable() && queue.isProcessWarm() && !shouldRetire; + return !queue.isRunnable() || !queue.isProcessWarm() || shouldRetire; } /** * Terminate all active broadcasts on the queue. */ - private boolean finishReceiverActiveLocked(@NonNull BroadcastProcessQueue queue, + private void finishReceiverActiveLocked(@NonNull BroadcastProcessQueue queue, @DeliveryState int deliveryState, @NonNull String reason) { if (!queue.isActive()) { logw("Ignoring finish; no active broadcast for " + queue); - return false; + return; } final int cookie = traceBegin("finishReceiver"); @@ -1077,27 +1108,63 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // Given that a receiver just finished, check if the "waitingFor" conditions are met. checkAndRemoveWaitingFor(); - final boolean res = shouldContinueScheduling(queue); - if (res) { - // We're on a roll; move onto the next broadcast for this process - queue.makeActiveNextPending(); - scheduleReceiverWarmLocked(queue); - } 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); - } traceEnd(cookie); - return res; + } + + /** + * Promote a process to the "running" list. + */ + @GuardedBy("mService") + private void promoteToRunningLocked(@NonNull BroadcastProcessQueue queue) { + // Allocate this available permit and start running! + final int queueIndex = getRunningIndexOf(null); + mRunning[queueIndex] = queue; + + // Remove ourselves from linked list of runnable things + mRunnableHead = removeFromRunnableList(mRunnableHead, queue); + + // Emit all trace events for this process into a consistent track + queue.runningTraceTrackName = TAG + ".mRunning[" + queueIndex + "]"; + queue.runningOomAdjusted = queue.isPendingManifest() + || queue.isPendingOrdered() + || queue.isPendingResultTo(); + + // If already warm, we can make OOM adjust request immediately; + // otherwise we need to wait until process becomes warm + final boolean processWarm = queue.isProcessWarm(); + 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(); + } else { + queue.traceProcessStartingBegin(); + } + } + + /** + * Demote a process from the "running" list. + */ + @GuardedBy("mService") + private void demoteFromRunningLocked(@NonNull BroadcastProcessQueue queue) { + final int cookie = traceBegin("demoteFromRunning"); + // 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); + traceEnd(cookie); } /** diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java index 64fe39314f0e6..a92744086f70c 100644 --- a/services/core/java/com/android/server/am/BroadcastRecord.java +++ b/services/core/java/com/android/server/am/BroadcastRecord.java @@ -237,6 +237,14 @@ final class BroadcastRecord extends Binder { } } + /** + * Return true if this receiver should be assumed to have been delivered. + */ + boolean isAssumedDelivered(int index) { + return (receivers.get(index) instanceof BroadcastFilter) && !ordered + && (resultTo == null); + } + ProcessRecord curApp; // hosting application of current receiver. ComponentName curComponent; // the receiver class that is currently running. ActivityInfo curReceiver; // the manifest receiver that is currently running.