Loading services/core/java/com/android/server/am/BroadcastProcessQueue.java +11 −12 Original line number Diff line number Diff line Loading @@ -188,7 +188,7 @@ class BroadcastProcessQueue { private @Reason int mRunnableAtReason = REASON_EMPTY; private boolean mRunnableAtInvalidated; private boolean mProcessCached; private boolean mUidCached; private boolean mProcessInstrumented; private boolean mProcessPersistent; Loading Loading @@ -382,14 +382,14 @@ class BroadcastProcessQueue { /** * Update the actively running "warm" process for this process. */ public void setProcess(@Nullable ProcessRecord app) { public void setProcessAndUidCached(@Nullable ProcessRecord app, boolean uidCached) { this.app = app; if (app != null) { setProcessCached(app.isCached()); setUidCached(uidCached); setProcessInstrumented(app.getActiveInstrumentation() != null); setProcessPersistent(app.isPersistent()); } else { setProcessCached(false); setUidCached(uidCached); setProcessInstrumented(false); setProcessPersistent(false); } Loading @@ -403,10 +403,9 @@ class BroadcastProcessQueue { * Update if this process is in the "cached" state, typically signaling that * broadcast dispatch should be paused or delayed. */ @VisibleForTesting void setProcessCached(boolean cached) { if (mProcessCached != cached) { mProcessCached = cached; private void setUidCached(boolean uidCached) { if (mUidCached != uidCached) { mUidCached = uidCached; invalidateRunnableAt(); } } Loading @@ -416,7 +415,7 @@ class BroadcastProcessQueue { * signaling that broadcast dispatch should bypass all pauses or delays, to * avoid holding up test suites. */ public void setProcessInstrumented(boolean instrumented) { private void setProcessInstrumented(boolean instrumented) { if (mProcessInstrumented != instrumented) { mProcessInstrumented = instrumented; invalidateRunnableAt(); Loading @@ -427,7 +426,7 @@ class BroadcastProcessQueue { * Update if this process is in the "persistent" state, which signals broadcast dispatch should * bypass all pauses or delays to prevent the system from becoming out of sync with itself. */ public void setProcessPersistent(boolean persistent) { private void setProcessPersistent(boolean persistent) { if (mProcessPersistent != persistent) { mProcessPersistent = persistent; invalidateRunnableAt(); Loading Loading @@ -986,7 +985,7 @@ class BroadcastProcessQueue { } else if (mProcessPersistent) { mRunnableAt = runnableAt; mRunnableAtReason = REASON_PERSISTENT; } else if (mProcessCached) { } else if (mUidCached) { if (r.deferUntilActive) { // All enqueued broadcasts are deferrable, defer if (mCountDeferred == mCountEnqueued) { Loading Loading @@ -1195,7 +1194,7 @@ class BroadcastProcessQueue { @NeverCompile private void dumpProcessState(@NonNull IndentingPrintWriter pw) { final StringBuilder sb = new StringBuilder(); if (mProcessCached) { if (mUidCached) { sb.append("CACHED"); } if (mProcessInstrumented) { Loading services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +38 −6 Original line number Diff line number Diff line Loading @@ -72,6 +72,7 @@ import android.util.MathUtils; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; Loading Loading @@ -209,6 +210,16 @@ class BroadcastQueueModernImpl extends BroadcastQueue { private final AtomicReference<ArraySet<BroadcastRecord>> mReplacedBroadcastsCache = new AtomicReference<>(); /** * Map from UID to its last known "cached" state. * <p> * 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; Loading Loading @@ -485,7 +496,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // relevant per-process queue final BroadcastProcessQueue queue = getProcessQueue(app); if (queue != null) { queue.setProcess(app); setQueueProcess(queue, app); } boolean didSomething = false; Loading Loading @@ -526,7 +537,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // relevant per-process queue final BroadcastProcessQueue queue = getProcessQueue(app); if (queue != null) { queue.setProcess(null); setQueueProcess(queue, null); } if ((mRunningColdStart != null) && (mRunningColdStart == queue)) { Loading Loading @@ -1332,11 +1343,17 @@ class BroadcastQueueModernImpl extends BroadcastQueue { @Override public void onUidCachedChanged(int uid, boolean cached) { synchronized (mService) { if (cached) { mUidCached.put(uid, true); } else { mUidCached.delete(uid); } BroadcastProcessQueue leaf = mProcessQueues.get(uid); while (leaf != null) { // Update internal state by refreshing values previously // read from any known running process leaf.setProcess(leaf.app); setQueueProcess(leaf, leaf.app); updateQueueDeferred(leaf); updateRunnableList(leaf); leaf = leaf.processNameNext; Loading Loading @@ -1498,10 +1515,19 @@ class BroadcastQueueModernImpl extends BroadcastQueue { private void updateWarmProcess(@NonNull BroadcastProcessQueue queue) { if (!queue.isProcessWarm()) { queue.setProcess(mService.getProcessRecordLocked(queue.processName, queue.uid)); setQueueProcess(queue, mService.getProcessRecordLocked(queue.processName, queue.uid)); } } /** * Update the {@link ProcessRecord} associated with the given * {@link BroadcastProcessQueue}. */ private void setQueueProcess(@NonNull BroadcastProcessQueue queue, @Nullable ProcessRecord app) { queue.setProcessAndUidCached(app, mUidCached.get(queue.uid, false)); } /** * Inform other parts of OS that the given broadcast queue has started * running, typically for internal bookkeeping. Loading Loading @@ -1692,7 +1718,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } BroadcastProcessQueue created = new BroadcastProcessQueue(mConstants, processName, uid); created.setProcess(mService.getProcessRecordLocked(processName, uid)); setQueueProcess(created, mService.getProcessRecordLocked(processName, uid)); if (leaf == null) { mProcessQueues.put(uid, created); Loading Loading @@ -1821,6 +1847,12 @@ class BroadcastQueueModernImpl extends BroadcastQueue { ipw.decreaseIndent(); ipw.println(); ipw.println("Cached UIDs:"); ipw.increaseIndent(); ipw.println(mUidCached.toString()); ipw.decreaseIndent(); ipw.println(); if (dumpConstants) { mConstants.dump(ipw); } Loading services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java +7 −7 Original line number Diff line number Diff line Loading @@ -372,9 +372,9 @@ public final class BroadcastQueueModernImplTest { List.of(makeMockRegisteredReceiver()), false); queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false); queue.setProcessCached(false); queue.setProcessAndUidCached(null, false); final long notCachedRunnableAt = queue.getRunnableAt(); queue.setProcessCached(true); queue.setProcessAndUidCached(null, true); final long cachedRunnableAt = queue.getRunnableAt(); assertThat(cachedRunnableAt).isGreaterThan(notCachedRunnableAt); assertFalse(queue.isRunnable()); Loading @@ -399,9 +399,9 @@ public final class BroadcastQueueModernImplTest { List.of(makeMockRegisteredReceiver()), false); queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false); queue.setProcessCached(false); queue.setProcessAndUidCached(null, false); final long notCachedRunnableAt = queue.getRunnableAt(); queue.setProcessCached(true); queue.setProcessAndUidCached(null, true); final long cachedRunnableAt = queue.getRunnableAt(); assertThat(cachedRunnableAt).isGreaterThan(notCachedRunnableAt); assertTrue(queue.isRunnable()); Loading Loading @@ -431,13 +431,13 @@ public final class BroadcastQueueModernImplTest { // verify that: // (a) the queue is immediately runnable by existence of a fg-priority broadcast // (b) the next one up is the fg-priority broadcast despite its later enqueue time queue.setProcessCached(false); queue.setProcessAndUidCached(null, false); assertTrue(queue.isRunnable()); assertThat(queue.getRunnableAt()).isAtMost(airplaneRecord.enqueueClockTime); assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked()); assertEquals(queue.peekNextBroadcastRecord(), airplaneRecord); queue.setProcessCached(true); queue.setProcessAndUidCached(null, true); assertTrue(queue.isRunnable()); assertThat(queue.getRunnableAt()).isAtMost(airplaneRecord.enqueueClockTime); assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked()); Loading Loading @@ -500,7 +500,7 @@ public final class BroadcastQueueModernImplTest { private void doRunnableAt_Cached(BroadcastRecord testRecord, int testRunnableAtReason) { final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants, PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN)); queue.setProcessCached(true); queue.setProcessAndUidCached(null, true); final BroadcastRecord lazyRecord = makeBroadcastRecord( new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), Loading services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +5 −8 Original line number Diff line number Diff line Loading @@ -1659,8 +1659,8 @@ public class BroadcastQueueTest { final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW); final ProcessRecord receiverOrangeApp = makeActiveProcessRecord(PACKAGE_ORANGE); receiverGreenApp.setCached(true); receiverBlueApp.setCached(true); mUidObserver.onUidCachedChanged(getUidForPackage(PACKAGE_GREEN), true); mUidObserver.onUidCachedChanged(getUidForPackage(PACKAGE_BLUE), true); final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK); final BroadcastOptions opts = BroadcastOptions.makeBasic() Loading Loading @@ -1704,13 +1704,11 @@ public class BroadcastQueueTest { eq(UserHandle.USER_SYSTEM), anyInt(), anyInt(), any()); // Shift blue to be active and confirm that deferred broadcast is delivered receiverBlueApp.setCached(false); mUidObserver.onUidCachedChanged(getUidForPackage(PACKAGE_BLUE), false); waitForIdle(); verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, timeTick); // Shift green to be active and confirm that deferred broadcast is delivered receiverGreenApp.setCached(false); mUidObserver.onUidCachedChanged(getUidForPackage(PACKAGE_GREEN), false); waitForIdle(); verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, timeTick); Loading Loading @@ -2044,9 +2042,9 @@ public class BroadcastQueueTest { final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE); final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW); receiverGreenApp.setCached(true); receiverBlueApp.setCached(true); receiverYellowApp.setCached(false); mUidObserver.onUidCachedChanged(getUidForPackage(PACKAGE_GREEN), true); mUidObserver.onUidCachedChanged(getUidForPackage(PACKAGE_BLUE), true); mUidObserver.onUidCachedChanged(getUidForPackage(PACKAGE_YELLOW), false); final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); final BroadcastOptions opts = BroadcastOptions.makeBasic() Loading @@ -2069,7 +2067,6 @@ public class BroadcastQueueTest { verifyScheduleRegisteredReceiver(times(1), receiverYellowApp, airplane); // Shift green to be active and confirm that deferred broadcast is delivered receiverGreenApp.setCached(false); mUidObserver.onUidCachedChanged(getUidForPackage(PACKAGE_GREEN), false); waitForIdle(); verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, airplane); Loading Loading
services/core/java/com/android/server/am/BroadcastProcessQueue.java +11 −12 Original line number Diff line number Diff line Loading @@ -188,7 +188,7 @@ class BroadcastProcessQueue { private @Reason int mRunnableAtReason = REASON_EMPTY; private boolean mRunnableAtInvalidated; private boolean mProcessCached; private boolean mUidCached; private boolean mProcessInstrumented; private boolean mProcessPersistent; Loading Loading @@ -382,14 +382,14 @@ class BroadcastProcessQueue { /** * Update the actively running "warm" process for this process. */ public void setProcess(@Nullable ProcessRecord app) { public void setProcessAndUidCached(@Nullable ProcessRecord app, boolean uidCached) { this.app = app; if (app != null) { setProcessCached(app.isCached()); setUidCached(uidCached); setProcessInstrumented(app.getActiveInstrumentation() != null); setProcessPersistent(app.isPersistent()); } else { setProcessCached(false); setUidCached(uidCached); setProcessInstrumented(false); setProcessPersistent(false); } Loading @@ -403,10 +403,9 @@ class BroadcastProcessQueue { * Update if this process is in the "cached" state, typically signaling that * broadcast dispatch should be paused or delayed. */ @VisibleForTesting void setProcessCached(boolean cached) { if (mProcessCached != cached) { mProcessCached = cached; private void setUidCached(boolean uidCached) { if (mUidCached != uidCached) { mUidCached = uidCached; invalidateRunnableAt(); } } Loading @@ -416,7 +415,7 @@ class BroadcastProcessQueue { * signaling that broadcast dispatch should bypass all pauses or delays, to * avoid holding up test suites. */ public void setProcessInstrumented(boolean instrumented) { private void setProcessInstrumented(boolean instrumented) { if (mProcessInstrumented != instrumented) { mProcessInstrumented = instrumented; invalidateRunnableAt(); Loading @@ -427,7 +426,7 @@ class BroadcastProcessQueue { * Update if this process is in the "persistent" state, which signals broadcast dispatch should * bypass all pauses or delays to prevent the system from becoming out of sync with itself. */ public void setProcessPersistent(boolean persistent) { private void setProcessPersistent(boolean persistent) { if (mProcessPersistent != persistent) { mProcessPersistent = persistent; invalidateRunnableAt(); Loading Loading @@ -986,7 +985,7 @@ class BroadcastProcessQueue { } else if (mProcessPersistent) { mRunnableAt = runnableAt; mRunnableAtReason = REASON_PERSISTENT; } else if (mProcessCached) { } else if (mUidCached) { if (r.deferUntilActive) { // All enqueued broadcasts are deferrable, defer if (mCountDeferred == mCountEnqueued) { Loading Loading @@ -1195,7 +1194,7 @@ class BroadcastProcessQueue { @NeverCompile private void dumpProcessState(@NonNull IndentingPrintWriter pw) { final StringBuilder sb = new StringBuilder(); if (mProcessCached) { if (mUidCached) { sb.append("CACHED"); } if (mProcessInstrumented) { Loading
services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +38 −6 Original line number Diff line number Diff line Loading @@ -72,6 +72,7 @@ import android.util.MathUtils; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; Loading Loading @@ -209,6 +210,16 @@ class BroadcastQueueModernImpl extends BroadcastQueue { private final AtomicReference<ArraySet<BroadcastRecord>> mReplacedBroadcastsCache = new AtomicReference<>(); /** * Map from UID to its last known "cached" state. * <p> * 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; Loading Loading @@ -485,7 +496,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // relevant per-process queue final BroadcastProcessQueue queue = getProcessQueue(app); if (queue != null) { queue.setProcess(app); setQueueProcess(queue, app); } boolean didSomething = false; Loading Loading @@ -526,7 +537,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // relevant per-process queue final BroadcastProcessQueue queue = getProcessQueue(app); if (queue != null) { queue.setProcess(null); setQueueProcess(queue, null); } if ((mRunningColdStart != null) && (mRunningColdStart == queue)) { Loading Loading @@ -1332,11 +1343,17 @@ class BroadcastQueueModernImpl extends BroadcastQueue { @Override public void onUidCachedChanged(int uid, boolean cached) { synchronized (mService) { if (cached) { mUidCached.put(uid, true); } else { mUidCached.delete(uid); } BroadcastProcessQueue leaf = mProcessQueues.get(uid); while (leaf != null) { // Update internal state by refreshing values previously // read from any known running process leaf.setProcess(leaf.app); setQueueProcess(leaf, leaf.app); updateQueueDeferred(leaf); updateRunnableList(leaf); leaf = leaf.processNameNext; Loading Loading @@ -1498,10 +1515,19 @@ class BroadcastQueueModernImpl extends BroadcastQueue { private void updateWarmProcess(@NonNull BroadcastProcessQueue queue) { if (!queue.isProcessWarm()) { queue.setProcess(mService.getProcessRecordLocked(queue.processName, queue.uid)); setQueueProcess(queue, mService.getProcessRecordLocked(queue.processName, queue.uid)); } } /** * Update the {@link ProcessRecord} associated with the given * {@link BroadcastProcessQueue}. */ private void setQueueProcess(@NonNull BroadcastProcessQueue queue, @Nullable ProcessRecord app) { queue.setProcessAndUidCached(app, mUidCached.get(queue.uid, false)); } /** * Inform other parts of OS that the given broadcast queue has started * running, typically for internal bookkeeping. Loading Loading @@ -1692,7 +1718,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } BroadcastProcessQueue created = new BroadcastProcessQueue(mConstants, processName, uid); created.setProcess(mService.getProcessRecordLocked(processName, uid)); setQueueProcess(created, mService.getProcessRecordLocked(processName, uid)); if (leaf == null) { mProcessQueues.put(uid, created); Loading Loading @@ -1821,6 +1847,12 @@ class BroadcastQueueModernImpl extends BroadcastQueue { ipw.decreaseIndent(); ipw.println(); ipw.println("Cached UIDs:"); ipw.increaseIndent(); ipw.println(mUidCached.toString()); ipw.decreaseIndent(); ipw.println(); if (dumpConstants) { mConstants.dump(ipw); } Loading
services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java +7 −7 Original line number Diff line number Diff line Loading @@ -372,9 +372,9 @@ public final class BroadcastQueueModernImplTest { List.of(makeMockRegisteredReceiver()), false); queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false); queue.setProcessCached(false); queue.setProcessAndUidCached(null, false); final long notCachedRunnableAt = queue.getRunnableAt(); queue.setProcessCached(true); queue.setProcessAndUidCached(null, true); final long cachedRunnableAt = queue.getRunnableAt(); assertThat(cachedRunnableAt).isGreaterThan(notCachedRunnableAt); assertFalse(queue.isRunnable()); Loading @@ -399,9 +399,9 @@ public final class BroadcastQueueModernImplTest { List.of(makeMockRegisteredReceiver()), false); queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false); queue.setProcessCached(false); queue.setProcessAndUidCached(null, false); final long notCachedRunnableAt = queue.getRunnableAt(); queue.setProcessCached(true); queue.setProcessAndUidCached(null, true); final long cachedRunnableAt = queue.getRunnableAt(); assertThat(cachedRunnableAt).isGreaterThan(notCachedRunnableAt); assertTrue(queue.isRunnable()); Loading Loading @@ -431,13 +431,13 @@ public final class BroadcastQueueModernImplTest { // verify that: // (a) the queue is immediately runnable by existence of a fg-priority broadcast // (b) the next one up is the fg-priority broadcast despite its later enqueue time queue.setProcessCached(false); queue.setProcessAndUidCached(null, false); assertTrue(queue.isRunnable()); assertThat(queue.getRunnableAt()).isAtMost(airplaneRecord.enqueueClockTime); assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked()); assertEquals(queue.peekNextBroadcastRecord(), airplaneRecord); queue.setProcessCached(true); queue.setProcessAndUidCached(null, true); assertTrue(queue.isRunnable()); assertThat(queue.getRunnableAt()).isAtMost(airplaneRecord.enqueueClockTime); assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked()); Loading Loading @@ -500,7 +500,7 @@ public final class BroadcastQueueModernImplTest { private void doRunnableAt_Cached(BroadcastRecord testRecord, int testRunnableAtReason) { final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants, PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN)); queue.setProcessCached(true); queue.setProcessAndUidCached(null, true); final BroadcastRecord lazyRecord = makeBroadcastRecord( new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), Loading
services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +5 −8 Original line number Diff line number Diff line Loading @@ -1659,8 +1659,8 @@ public class BroadcastQueueTest { final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW); final ProcessRecord receiverOrangeApp = makeActiveProcessRecord(PACKAGE_ORANGE); receiverGreenApp.setCached(true); receiverBlueApp.setCached(true); mUidObserver.onUidCachedChanged(getUidForPackage(PACKAGE_GREEN), true); mUidObserver.onUidCachedChanged(getUidForPackage(PACKAGE_BLUE), true); final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK); final BroadcastOptions opts = BroadcastOptions.makeBasic() Loading Loading @@ -1704,13 +1704,11 @@ public class BroadcastQueueTest { eq(UserHandle.USER_SYSTEM), anyInt(), anyInt(), any()); // Shift blue to be active and confirm that deferred broadcast is delivered receiverBlueApp.setCached(false); mUidObserver.onUidCachedChanged(getUidForPackage(PACKAGE_BLUE), false); waitForIdle(); verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, timeTick); // Shift green to be active and confirm that deferred broadcast is delivered receiverGreenApp.setCached(false); mUidObserver.onUidCachedChanged(getUidForPackage(PACKAGE_GREEN), false); waitForIdle(); verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, timeTick); Loading Loading @@ -2044,9 +2042,9 @@ public class BroadcastQueueTest { final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE); final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW); receiverGreenApp.setCached(true); receiverBlueApp.setCached(true); receiverYellowApp.setCached(false); mUidObserver.onUidCachedChanged(getUidForPackage(PACKAGE_GREEN), true); mUidObserver.onUidCachedChanged(getUidForPackage(PACKAGE_BLUE), true); mUidObserver.onUidCachedChanged(getUidForPackage(PACKAGE_YELLOW), false); final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); final BroadcastOptions opts = BroadcastOptions.makeBasic() Loading @@ -2069,7 +2067,6 @@ public class BroadcastQueueTest { verifyScheduleRegisteredReceiver(times(1), receiverYellowApp, airplane); // Shift green to be active and confirm that deferred broadcast is delivered receiverGreenApp.setCached(false); mUidObserver.onUidCachedChanged(getUidForPackage(PACKAGE_GREEN), false); waitForIdle(); verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, airplane); Loading