From 91fb9aee8767c908a2969177f0ae4999e4226874 Mon Sep 17 00:00:00 2001 From: Sudheer Shanka Date: Fri, 12 May 2023 12:16:41 -0700 Subject: [PATCH] Track per-process freezable state in BroadcastQueue. This per-process freezable state will be used for deciding when to start deferring broadcasts to a process. Bug: 275142200 Test: atest services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java Test: atest services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java Test: atest ./tests/app/BroadcastsTest/src/android/app/cts/broadcasts/BroadcastDeferralTest.java Test: atest ./tests/app/BroadcastsTest/src/android/app/cts/broadcasts/BroadcastDeliveryGroupTest.java Change-Id: Ia480df083ed2ca213822e722fbbccaa8d6c7d33f --- .../server/am/ActivityManagerService.java | 6 ++ .../server/am/BroadcastProcessQueue.java | 24 +++--- .../com/android/server/am/BroadcastQueue.java | 7 ++ .../android/server/am/BroadcastQueueImpl.java | 4 + .../server/am/BroadcastQueueModernImpl.java | 72 +++++++++--------- .../android/server/am/CachedAppOptimizer.java | 38 ++++++++-- .../com/android/server/am/ProcessRecord.java | 3 + .../am/BroadcastQueueModernImplTest.java | 8 +- .../android/server/am/BroadcastQueueTest.java | 73 +++++++++++++++++-- 9 files changed, 167 insertions(+), 68 deletions(-) diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index bfa397fa2b036..ca4a253e779c4 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -17236,6 +17236,12 @@ public class ActivityManagerService extends IActivityManager.Stub } } + void onProcessFreezableChangedLocked(ProcessRecord app) { + if (mEnableModernQueue) { + mBroadcastQueues[0].onProcessFreezableChangedLocked(app); + } + } + @VisibleForTesting public final class LocalService extends ActivityManagerInternal implements ActivityManagerLocal { diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index 3ac2b2bd9fd1b..142a5dc6db0fb 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -201,7 +201,7 @@ class BroadcastProcessQueue { private boolean mLastDeferredStates; private boolean mUidForeground; - private boolean mUidCached; + private boolean mProcessFreezable; private boolean mProcessInstrumented; private boolean mProcessPersistent; @@ -411,7 +411,7 @@ class BroadcastProcessQueue { */ @CheckResult public boolean setProcessAndUidState(@Nullable ProcessRecord app, boolean uidForeground, - boolean uidCached) { + boolean processFreezable) { this.app = app; // Since we may have just changed our PID, invalidate cached strings @@ -420,13 +420,13 @@ class BroadcastProcessQueue { boolean didSomething = false; if (app != null) { - didSomething |= setUidCached(uidCached); didSomething |= setUidForeground(uidForeground); + didSomething |= setProcessFreezable(processFreezable); didSomething |= setProcessInstrumented(app.getActiveInstrumentation() != null); didSomething |= setProcessPersistent(app.isPersistent()); } else { - didSomething |= setUidCached(uidCached); didSomething |= setUidForeground(false); + didSomething |= setProcessFreezable(false); didSomething |= setProcessInstrumented(false); didSomething |= setProcessPersistent(false); } @@ -450,13 +450,13 @@ class BroadcastProcessQueue { } /** - * Update if this process is in the "cached" state, typically signaling that + * Update if this process is in the "freezable" state, typically signaling that * broadcast dispatch should be paused or delayed. */ @CheckResult - private boolean setUidCached(boolean uidCached) { - if (mUidCached != uidCached) { - mUidCached = uidCached; + private boolean setProcessFreezable(boolean freezable) { + if (mProcessFreezable != freezable) { + mProcessFreezable = freezable; invalidateRunnableAt(); return true; } else { @@ -1115,7 +1115,7 @@ class BroadcastProcessQueue { } else if (mCountManifest > 0) { mRunnableAt = runnableAt; mRunnableAtReason = REASON_CONTAINS_MANIFEST; - } else if (mUidCached) { + } else if (mProcessFreezable) { if (r.deferUntilActive) { // All enqueued broadcasts are deferrable, defer if (mCountDeferred == mCountEnqueued) { @@ -1185,7 +1185,7 @@ class BroadcastProcessQueue { // When all we have pending is deferred broadcasts, and we're cached, // then we want everything to be marked deferred final boolean wantDeferredStates = (mCountDeferred > 0) - && (mCountDeferred == mCountEnqueued) && mUidCached; + && (mCountDeferred == mCountEnqueued) && mProcessFreezable; if (mLastDeferredStates != wantDeferredStates) { mLastDeferredStates = wantDeferredStates; @@ -1372,9 +1372,9 @@ class BroadcastProcessQueue { if (mUidForeground) { sb.append("FG"); } - if (mUidCached) { + if (mProcessFreezable) { if (sb.length() > 0) sb.append("|"); - sb.append("CACHED"); + sb.append("FRZN"); } if (mProcessInstrumented) { if (sb.length() > 0) sb.append("|"); diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 8e76e5b5cf484..93bde6f1132ba 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -172,6 +172,13 @@ public abstract class BroadcastQueue { @GuardedBy("mService") public abstract void onApplicationCleanupLocked(@NonNull ProcessRecord app); + /** + * Signal from OS internals that the given process is in a freezable state and will be + * freezed soon after. + */ + @GuardedBy("mService") + public abstract void onProcessFreezableChangedLocked(@NonNull ProcessRecord app); + /** * Signal from OS internals that the given package (or some subset of that * package) has been disabled or uninstalled, and that any pending diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java index 7f3ceb578891f..ce635f1076c11 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java @@ -449,6 +449,10 @@ public class BroadcastQueueImpl extends BroadcastQueue { skipCurrentOrPendingReceiverLocked(app); } + public void onProcessFreezableChangedLocked(ProcessRecord app) { + // Not supported; ignore + } + public boolean sendPendingBroadcastsLocked(ProcessRecord app) { boolean didSomething = false; final BroadcastRecord br = mPendingBroadcast; diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index d9b315794ea3f..3e0f71a57bbe4 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -222,16 +222,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue { @GuardedBy("mService") private final SparseBooleanArray mUidForeground = new SparseBooleanArray(); - /** - * Map from UID to its last known "cached" state. - *

- * We manually maintain this data structure since the lifecycle of - * {@link ProcessRecord} and {@link BroadcastProcessQueue} can be - * mismatched. - */ - @GuardedBy("mService") - private final SparseBooleanArray mUidCached = new SparseBooleanArray(); - private final BroadcastConstants mConstants; private final BroadcastConstants mFgConstants; private final BroadcastConstants mBgConstants; @@ -573,6 +563,13 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } } + @Override + public void onProcessFreezableChangedLocked(@NonNull ProcessRecord app) { + synchronized (mService) { + refreshProcessQueueLocked(app); + } + } + @Override public int getPreferredSchedulingGroupLocked(@NonNull ProcessRecord app) { final BroadcastProcessQueue queue = getProcessQueue(app); @@ -1296,7 +1293,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue { }; broadcastPredicate = BROADCAST_PREDICATE_ANY; - cleanupUserStateLocked(mUidCached, userId); cleanupUserStateLocked(mUidForeground, userId); } return forEachMatchingBroadcast(queuePredicate, broadcastPredicate, @@ -1439,20 +1435,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { refreshProcessQueuesLocked(uid); } } - - @Override - public void onUidCachedChanged(int uid, boolean cached) { - synchronized (mService) { - if (cached) { - mUidCached.put(uid, true); - } else { - mUidCached.delete(uid); - } - refreshProcessQueuesLocked(uid); - } - } - }, ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_CACHED, - ActivityManager.PROCESS_STATE_TOP, "android"); + }, ActivityManager.UID_OBSERVER_PROCSTATE, ActivityManager.PROCESS_STATE_TOP, "android"); // Kick off periodic health checks mLocalHandler.sendEmptyMessage(MSG_CHECK_HEALTH); @@ -1641,10 +1624,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // warm via this operation, we're going to immediately promote it to // be running, and any side effect of this operation will then apply // after it's finished and is returned to the runnable list. - queue.setProcessAndUidState( - mService.getProcessRecordLocked(queue.processName, queue.uid), - mUidForeground.get(queue.uid, false), - mUidCached.get(queue.uid, false)); + final ProcessRecord app = mService.getProcessRecordLocked(queue.processName, queue.uid); + queue.setProcessAndUidState(app, mUidForeground.get(queue.uid, false), + isProcessFreezable(app)); } } @@ -1656,11 +1638,21 @@ class BroadcastQueueModernImpl extends BroadcastQueue { private void setQueueProcess(@NonNull BroadcastProcessQueue queue, @Nullable ProcessRecord app) { if (queue.setProcessAndUidState(app, mUidForeground.get(queue.uid, false), - mUidCached.get(queue.uid, false))) { + isProcessFreezable(app))) { updateRunnableList(queue); } } + @GuardedBy("mService") + private boolean isProcessFreezable(@Nullable ProcessRecord app) { + if (app == null) { + return false; + } + synchronized (mService.mProcLock) { + return app.mOptRecord.isPendingFreeze() || app.mOptRecord.isFrozen(); + } + } + /** * Refresh the process queues with the latest process state so that runnableAt * can be updated. @@ -1677,6 +1669,20 @@ class BroadcastQueueModernImpl extends BroadcastQueue { enqueueUpdateRunningList(); } + /** + * Refresh the process queue corresponding to {@code app} with the latest process state + * so that runnableAt can be updated. + */ + @GuardedBy("mService") + private void refreshProcessQueueLocked(@NonNull ProcessRecord app) { + final BroadcastProcessQueue queue = getProcessQueue(app.processName, app.uid); + if (queue == null || queue.app == null || queue.app.getPid() != app.getPid()) { + return; + } + setQueueProcess(queue, queue.app); + enqueueUpdateRunningList(); + } + /** * Inform other parts of OS that the given broadcast queue has started * running, typically for internal bookkeeping. @@ -1996,12 +2002,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue { ipw.decreaseIndent(); ipw.println(); - ipw.println("Cached UIDs:"); - ipw.increaseIndent(); - ipw.println(mUidCached); - ipw.decreaseIndent(); - ipw.println(); - ipw.println("Foreground UIDs:"); ipw.increaseIndent(); ipw.println(mUidForeground); diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index 3e82d557c01ae..28ed2cba9f197 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -299,6 +299,7 @@ public final class CachedAppOptimizer { static final int COMPACT_NATIVE_MSG = 5; static final int UID_FROZEN_STATE_CHANGED_MSG = 6; static final int DEADLOCK_WATCHDOG_MSG = 7; + static final int REPORT_PROCESS_FREEZABLE_CHANGED = 8; // When free swap falls below this percentage threshold any full (file + anon) // compactions will be downgraded to file only compactions to reduce pressure @@ -1324,6 +1325,7 @@ public final class CachedAppOptimizer { } } } + postProcessFreezableChangedMessage(app); mFreezeHandler.sendMessageDelayed( mFreezeHandler.obtainMessage(SET_FROZEN_PROCESS_MSG, DO_FREEZE, 0, app), delayMillis); @@ -1361,6 +1363,7 @@ public final class CachedAppOptimizer { uidRec.setFrozen(false); postUidFrozenMessage(uidRec.getUid(), false); } + postProcessFreezableChangedMessage(app); opt.setFreezerOverride(false); if (pid == 0 || !opt.isFrozen()) { @@ -2038,6 +2041,21 @@ public final class CachedAppOptimizer { 0, uidObj)); } + private void reportProcessFreezableChanged(ProcessRecord app) { + synchronized (mAm) { + mAm.onProcessFreezableChangedLocked(app); + } + } + + @GuardedBy("mAm") + private void postProcessFreezableChangedMessage(ProcessRecord app) { + if (app.getPid() == 0) { + return; + } + mFreezeHandler.obtainMessage(REPORT_PROCESS_FREEZABLE_CHANGED, 0, 0, app) + .sendToTarget(); + } + private final class FreezeHandler extends Handler implements ProcLocksReader.ProcLocksReaderCallback { private FreezeHandler() { @@ -2047,7 +2065,7 @@ public final class CachedAppOptimizer { @Override public void handleMessage(Message msg) { switch (msg.what) { - case SET_FROZEN_PROCESS_MSG: + case SET_FROZEN_PROCESS_MSG: { ProcessRecord proc = (ProcessRecord) msg.obj; synchronized (mAm) { freezeProcess(proc); @@ -2057,8 +2075,8 @@ public final class CachedAppOptimizer { removeMessages(DEADLOCK_WATCHDOG_MSG); sendEmptyMessageDelayed(DEADLOCK_WATCHDOG_MSG, FREEZE_DEADLOCK_TIMEOUT_MS); } - break; - case REPORT_UNFREEZE_MSG: + } break; + case REPORT_UNFREEZE_MSG: { int pid = msg.arg1; int frozenDuration = msg.arg2; Pair obj = (Pair) msg.obj; @@ -2066,13 +2084,13 @@ public final class CachedAppOptimizer { int reason = obj.second; reportUnfreeze(pid, frozenDuration, processName, reason); - break; - case UID_FROZEN_STATE_CHANGED_MSG: + } break; + case UID_FROZEN_STATE_CHANGED_MSG: { final boolean frozen = (msg.arg1 == 1); final int uid = (int) msg.obj; reportOneUidFrozenStateChanged(uid, frozen); - break; - case DEADLOCK_WATCHDOG_MSG: + } break; + case DEADLOCK_WATCHDOG_MSG: { try { // post-check to prevent deadlock if (DEBUG_FREEZER) { @@ -2082,7 +2100,11 @@ public final class CachedAppOptimizer { } catch (IOException e) { Slog.w(TAG_AM, "Unable to check file locks"); } - break; + } break; + case REPORT_PROCESS_FREEZABLE_CHANGED: { + final ProcessRecord app = (ProcessRecord) msg.obj; + reportProcessFreezableChanged(app); + } break; default: return; } diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 335d6768c37be..9b1149aed977b 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -1192,6 +1192,9 @@ class ProcessRecord implements WindowProcessListener { "Killing " + toShortString() + " (adj " + mState.getSetAdj() + "): " + reason, info.uid); } + // Since the process is getting killed, reset the freezable related state. + mOptRecord.setPendingFreeze(false); + mOptRecord.setFrozen(false); if (mPid > 0) { mService.mProcessList.noteAppKill(this, reasonCode, subReason, description); EventLog.writeEvent(EventLogTags.AM_KILL, diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java index f47954baf6494..2b4461537e9c9 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java @@ -393,9 +393,9 @@ public final class BroadcastQueueModernImplTest { List.of(makeMockRegisteredReceiver()), false); enqueueOrReplaceBroadcast(queue, airplaneRecord, 0); - queue.setProcessAndUidState(null, false, false); + queue.setProcessAndUidState(mProcess, false, false); final long notCachedRunnableAt = queue.getRunnableAt(); - queue.setProcessAndUidState(null, false, true); + queue.setProcessAndUidState(mProcess, false, true); final long cachedRunnableAt = queue.getRunnableAt(); assertThat(cachedRunnableAt).isGreaterThan(notCachedRunnableAt); assertFalse(queue.isRunnable()); @@ -420,9 +420,9 @@ public final class BroadcastQueueModernImplTest { List.of(makeMockRegisteredReceiver()), false); enqueueOrReplaceBroadcast(queue, airplaneRecord, 0); - queue.setProcessAndUidState(null, false, false); + queue.setProcessAndUidState(mProcess, false, false); final long notCachedRunnableAt = queue.getRunnableAt(); - queue.setProcessAndUidState(null, false, true); + queue.setProcessAndUidState(mProcess, false, true); final long cachedRunnableAt = queue.getRunnableAt(); assertThat(cachedRunnableAt).isGreaterThan(notCachedRunnableAt); assertTrue(queue.isRunnable()); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java index ad5f0d7233cac..615b4c1e89639 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -399,6 +399,12 @@ public class BroadcastQueueTest { UserHandle.USER_SYSTEM); } + private ProcessRecord makeActiveProcessRecord(String packageName, String processName) + throws Exception { + return makeActiveProcessRecord(packageName, processName, ProcessBehavior.NORMAL, + UserHandle.USER_SYSTEM); + } + private ProcessRecord makeActiveProcessRecord(String packageName, ProcessBehavior behavior) throws Exception { return makeActiveProcessRecord(packageName, packageName, behavior, UserHandle.USER_SYSTEM); @@ -596,6 +602,11 @@ public class BroadcastQueueTest { BackgroundStartPrivileges.NONE, false, null); } + private void setProcessFreezable(ProcessRecord app, boolean pendingFreeze, boolean frozen) { + app.mOptRecord.setPendingFreeze(pendingFreeze); + app.mOptRecord.setFrozen(frozen); + } + private void assertHealth() { if (mImpl == Impl.MODERN) { // If this fails, it'll throw a clear reason message @@ -1659,8 +1670,10 @@ public class BroadcastQueueTest { final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW); final ProcessRecord receiverOrangeApp = makeActiveProcessRecord(PACKAGE_ORANGE); - mUidObserver.onUidCachedChanged(getUidForPackage(PACKAGE_GREEN), true); - mUidObserver.onUidCachedChanged(getUidForPackage(PACKAGE_BLUE), true); + setProcessFreezable(receiverGreenApp, true, false); + mQueue.onProcessFreezableChangedLocked(receiverGreenApp); + setProcessFreezable(receiverBlueApp, false, true); + mQueue.onProcessFreezableChangedLocked(receiverBlueApp); final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK); final BroadcastOptions opts = BroadcastOptions.makeBasic() @@ -1704,12 +1717,14 @@ public class BroadcastQueueTest { eq(UserHandle.USER_SYSTEM), anyInt(), anyInt(), any()); // Shift blue to be active and confirm that deferred broadcast is delivered - mUidObserver.onUidCachedChanged(getUidForPackage(PACKAGE_BLUE), false); + setProcessFreezable(receiverBlueApp, false, false); + mQueue.onProcessFreezableChangedLocked(receiverBlueApp); waitForIdle(); verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, timeTick); // Shift green to be active and confirm that deferred broadcast is delivered - mUidObserver.onUidCachedChanged(getUidForPackage(PACKAGE_GREEN), false); + setProcessFreezable(receiverGreenApp, false, false); + mQueue.onProcessFreezableChangedLocked(receiverGreenApp); waitForIdle(); verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, timeTick); } @@ -2106,9 +2121,12 @@ public class BroadcastQueueTest { final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE); final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW); - mUidObserver.onUidCachedChanged(getUidForPackage(PACKAGE_GREEN), true); - mUidObserver.onUidCachedChanged(getUidForPackage(PACKAGE_BLUE), true); - mUidObserver.onUidCachedChanged(getUidForPackage(PACKAGE_YELLOW), false); + setProcessFreezable(receiverGreenApp, true, true); + mQueue.onProcessFreezableChangedLocked(receiverGreenApp); + setProcessFreezable(receiverBlueApp, true, false); + mQueue.onProcessFreezableChangedLocked(receiverBlueApp); + setProcessFreezable(receiverYellowApp, false, false); + mQueue.onProcessFreezableChangedLocked(receiverYellowApp); final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); final BroadcastOptions opts = BroadcastOptions.makeBasic() @@ -2131,11 +2149,50 @@ public class BroadcastQueueTest { verifyScheduleRegisteredReceiver(times(1), receiverYellowApp, airplane); // Shift green to be active and confirm that deferred broadcast is delivered - mUidObserver.onUidCachedChanged(getUidForPackage(PACKAGE_GREEN), false); + setProcessFreezable(receiverGreenApp, false, false); + mQueue.onProcessFreezableChangedLocked(receiverGreenApp); waitForIdle(); verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, airplane); } + /** + * Verify broadcasts to a runtime receiver in cached process is deferred even when a different + * process in the same package is not cached. + */ + @Test + public void testDeferralPolicy_UntilActive_WithMultiProcessUid() throws Exception { + // Legacy stack doesn't support deferral + Assume.assumeTrue(mImpl == Impl.MODERN); + + final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); + final ProcessRecord receiverGreenApp1 = makeActiveProcessRecord(PACKAGE_GREEN); + final ProcessRecord receiverGreenApp2 = makeActiveProcessRecord(PACKAGE_GREEN, + PACKAGE_GREEN + "_proc2"); + + setProcessFreezable(receiverGreenApp1, true, true); + mQueue.onProcessFreezableChangedLocked(receiverGreenApp1); + + final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + final BroadcastOptions opts = BroadcastOptions.makeBasic() + .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE); + enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, opts, + List.of(makeRegisteredReceiver(receiverGreenApp1), + makeRegisteredReceiver(receiverGreenApp2)))); + waitForIdle(); + + // 1st process in Green package is ignored since it is in a cached state + // but the 2nd process should still receive the broadcast. + verifyScheduleRegisteredReceiver(never(), receiverGreenApp1, airplane); + verifyScheduleRegisteredReceiver(times(1), receiverGreenApp2, airplane); + + // Shift the 1st process in Green package to be active and confirm that deferred broadcast + // is delivered + setProcessFreezable(receiverGreenApp1, false, false); + mQueue.onProcessFreezableChangedLocked(receiverGreenApp1); + waitForIdle(); + verifyScheduleRegisteredReceiver(times(1), receiverGreenApp1, airplane); + } + @Test public void testBroadcastDelivery_uidForeground() throws Exception { // Legacy stack doesn't support prioritization to foreground app.