diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index a7db663191af0..476e1b4ef11e1 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -17311,6 +17311,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 { @@ -19022,25 +19028,27 @@ public class ActivityManagerService extends IActivityManager.Stub // too quickly in parallel below pingCount.incrementAndGet(); - synchronized (mProcLock) { - final ArrayMap> pmap = - mProcessList.getProcessNamesLOSP().getMap(); - final int numProc = pmap.size(); - for (int iProc = 0; iProc < numProc; iProc++) { - final SparseArray apps = pmap.valueAt(iProc); - for (int iApp = 0, numApps = apps.size(); iApp < numApps; iApp++) { - final ProcessRecord app = apps.valueAt(iApp); - final IApplicationThread thread = app.getOnewayThread(); - if (thread != null) { - mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(app, - CachedAppOptimizer.UNFREEZE_REASON_PING); - pingCount.incrementAndGet(); - try { - thread.schedulePing(pongCallback); - } catch (RemoteException ignored) { - // When we failed to ping remote process, pretend as - // if we received the expected pong - pongCallback.sendResult(null); + synchronized (ActivityManagerService.this) { + synchronized (mProcLock) { + final ArrayMap> pmap = + mProcessList.getProcessNamesLOSP().getMap(); + final int numProc = pmap.size(); + for (int iProc = 0; iProc < numProc; iProc++) { + final SparseArray apps = pmap.valueAt(iProc); + for (int iApp = 0, numApps = apps.size(); iApp < numApps; iApp++) { + final ProcessRecord app = apps.valueAt(iApp); + final IApplicationThread thread = app.getOnewayThread(); + if (thread != null) { + mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(app, + CachedAppOptimizer.UNFREEZE_REASON_PING); + pingCount.incrementAndGet(); + try { + thread.schedulePing(pongCallback); + } catch (RemoteException ignored) { + // When we failed to ping remote process, pretend as + // if we received the expected pong + pongCallback.sendResult(null); + } } } } diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index a80ad599a3e2c..58f62b67ae11d 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -208,7 +208,7 @@ class BroadcastProcessQueue { private boolean mLastDeferredStates; private boolean mUidForeground; - private boolean mUidCached; + private boolean mProcessFreezable; private boolean mProcessInstrumented; private boolean mProcessPersistent; @@ -440,7 +440,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 @@ -449,13 +449,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); } @@ -479,13 +479,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 { @@ -1150,7 +1150,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) { @@ -1220,7 +1220,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; @@ -1407,9 +1407,9 @@ class BroadcastProcessQueue { if (mUidForeground) { sb.append("FG"); } - if (mUidCached) { + if (mProcessFreezable) { if (sb.length() > 0) sb.append("|"); - sb.append("CACHED"); + sb.append("FRZ"); } 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 e38a2eefcd92f..c1f1dfd4fe753 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -173,6 +173,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 + * frozen 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 f13dc89f2bd24..127c5b389d79a 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java @@ -466,6 +466,10 @@ public class BroadcastQueueImpl extends BroadcastQueue { skipCurrentOrPendingReceiverLocked(app); } + public void onProcessFreezableChangedLocked(ProcessRecord app) { + // Not supported; ignore + } + public boolean sendPendingBroadcastsLocked(ProcessRecord app) throws BroadcastDeliveryFailedException { boolean didSomething = false; diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index 03d1fbbad36f8..f420619db4907 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -232,16 +232,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; @@ -258,6 +248,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { private static final int MSG_BG_ACTIVITY_START_TIMEOUT = 4; private static final int MSG_CHECK_HEALTH = 5; private static final int MSG_CHECK_PENDING_COLD_START_VALIDITY = 6; + private static final int MSG_PROCESS_FREEZABLE_CHANGED = 7; private void enqueueUpdateRunningList() { mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST); @@ -298,6 +289,12 @@ class BroadcastQueueModernImpl extends BroadcastQueue { checkPendingColdStartValidity(); return true; } + case MSG_PROCESS_FREEZABLE_CHANGED: { + synchronized (mService) { + refreshProcessQueueLocked((ProcessRecord) msg.obj); + } + return true; + } } return false; }; @@ -672,6 +669,12 @@ class BroadcastQueueModernImpl extends BroadcastQueue { enqueueUpdateRunningList(); } + @Override + public void onProcessFreezableChangedLocked(@NonNull ProcessRecord app) { + mLocalHandler.removeMessages(MSG_PROCESS_FREEZABLE_CHANGED, app); + mLocalHandler.sendMessage(mHandler.obtainMessage(MSG_PROCESS_FREEZABLE_CHANGED, app)); + } + @Override public int getPreferredSchedulingGroupLocked(@NonNull ProcessRecord app) { final BroadcastProcessQueue queue = getProcessQueue(app); @@ -1465,7 +1468,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue { }; broadcastPredicate = BROADCAST_PREDICATE_ANY; - cleanupUserStateLocked(mUidCached, userId); cleanupUserStateLocked(mUidForeground, userId); } return forEachMatchingBroadcast(queuePredicate, broadcastPredicate, @@ -1611,22 +1613,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue { }, ActivityManager.UID_OBSERVER_PROCSTATE, ActivityManager.PROCESS_STATE_TOP, "android"); - mService.registerUidObserver(new UidObserver() { - @Override - public void onUidStateChanged(int uid, int procState, long procStateSeq, - int capability) { - synchronized (mService) { - if (procState > ActivityManager.PROCESS_STATE_LAST_ACTIVITY) { - mUidCached.put(uid, true); - } else { - mUidCached.delete(uid); - } - refreshProcessQueuesLocked(uid); - } - } - }, ActivityManager.UID_OBSERVER_PROCSTATE, - ActivityManager.PROCESS_STATE_LAST_ACTIVITY, "android"); - // Kick off periodic health checks mLocalHandler.sendEmptyMessage(MSG_CHECK_HEALTH); } @@ -1814,10 +1800,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)); } } @@ -1829,11 +1814,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. @@ -1850,6 +1845,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. @@ -2176,12 +2185,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 eccff2a74eaca..e588a9e51cd97 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -1402,6 +1402,7 @@ public final class CachedAppOptimizer { } } } + reportProcessFreezableChangedLocked(app); app.mOptRecord.setLastUsedTimeout(delayMillis); mFreezeHandler.sendMessageDelayed( mFreezeHandler.obtainMessage(SET_FROZEN_PROCESS_MSG, DO_FREEZE, 0, app), @@ -1440,6 +1441,7 @@ public final class CachedAppOptimizer { uidRec.setFrozen(false); postUidFrozenMessage(uidRec.getUid(), false); } + reportProcessFreezableChangedLocked(app); opt.setFreezerOverride(false); if (pid == 0 || !opt.isFrozen()) { @@ -2121,6 +2123,11 @@ public final class CachedAppOptimizer { 0, uidObj)); } + @GuardedBy("mAm") + private void reportProcessFreezableChangedLocked(ProcessRecord app) { + mAm.onProcessFreezableChangedLocked(app); + } + private final class FreezeHandler extends Handler implements ProcLocksReader.ProcLocksReaderCallback { private FreezeHandler() { @@ -2130,7 +2137,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); @@ -2140,8 +2147,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; @@ -2149,13 +2156,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) { @@ -2165,7 +2172,7 @@ public final class CachedAppOptimizer { } catch (IOException e) { Slog.w(TAG_AM, "Unable to check file locks"); } - break; + } 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 267d2464e0d52..f814c3b3112da 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -1197,6 +1197,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 f4238f678f08b..3b3e7cdb496c7 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java @@ -410,9 +410,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()); @@ -437,9 +437,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 7a88069b9173c..73eb237fa9e76 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -421,6 +421,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); @@ -623,6 +629,11 @@ public class BroadcastQueueTest { BackgroundStartPrivileges.NONE, false, null, PROCESS_STATE_UNKNOWN); } + 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 @@ -1769,12 +1780,10 @@ public class BroadcastQueueTest { final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW); final ProcessRecord receiverOrangeApp = makeActiveProcessRecord(PACKAGE_ORANGE); - mUidCachedStateObserver.onUidStateChanged(getUidForPackage(PACKAGE_GREEN), - ActivityManager.PROCESS_STATE_CACHED_ACTIVITY, 0, - ActivityManager.PROCESS_CAPABILITY_NONE); - mUidCachedStateObserver.onUidStateChanged(getUidForPackage(PACKAGE_BLUE), - ActivityManager.PROCESS_STATE_CACHED_EMPTY, 0, - ActivityManager.PROCESS_CAPABILITY_NONE); + 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() @@ -1818,16 +1827,14 @@ public class BroadcastQueueTest { eq(UserHandle.USER_SYSTEM), anyInt(), anyInt(), any()); // Shift blue to be active and confirm that deferred broadcast is delivered - mUidCachedStateObserver.onUidStateChanged(getUidForPackage(PACKAGE_BLUE), - ActivityManager.PROCESS_STATE_TOP, 0, - ActivityManager.PROCESS_CAPABILITY_NONE); + 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 - mUidCachedStateObserver.onUidStateChanged(getUidForPackage(PACKAGE_GREEN), - ActivityManager.PROCESS_STATE_SERVICE, 0, - ActivityManager.PROCESS_CAPABILITY_NONE); + setProcessFreezable(receiverGreenApp, false, false); + mQueue.onProcessFreezableChangedLocked(receiverGreenApp); waitForIdle(); verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, timeTick); } @@ -2252,15 +2259,12 @@ public class BroadcastQueueTest { final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE); final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW); - mUidCachedStateObserver.onUidStateChanged(getUidForPackage(PACKAGE_GREEN), - ActivityManager.PROCESS_STATE_CACHED_ACTIVITY, 0, - ActivityManager.PROCESS_CAPABILITY_NONE); - mUidCachedStateObserver.onUidStateChanged(getUidForPackage(PACKAGE_BLUE), - ActivityManager.PROCESS_STATE_CACHED_EMPTY, 0, - ActivityManager.PROCESS_CAPABILITY_NONE); - mUidCachedStateObserver.onUidStateChanged(getUidForPackage(PACKAGE_YELLOW), - ActivityManager.PROCESS_STATE_SERVICE, 0, - ActivityManager.PROCESS_CAPABILITY_NONE); + 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() @@ -2283,13 +2287,50 @@ public class BroadcastQueueTest { verifyScheduleRegisteredReceiver(times(1), receiverYellowApp, airplane); // Shift green to be active and confirm that deferred broadcast is delivered - mUidCachedStateObserver.onUidStateChanged(getUidForPackage(PACKAGE_GREEN), - ActivityManager.PROCESS_STATE_TOP_SLEEPING, 0, - ActivityManager.PROCESS_CAPABILITY_NONE); + 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.