Merge "Add a callback to listen to uid foreground-ness change." into udc-dev

This commit is contained in:
Sudheer Shanka
2023-05-09 22:20:15 +00:00
committed by Android (Google) Code Review
5 changed files with 200 additions and 33 deletions

View File

@@ -200,6 +200,7 @@ class BroadcastProcessQueue {
*/ */
private boolean mLastDeferredStates; private boolean mLastDeferredStates;
private boolean mUidForeground;
private boolean mUidCached; private boolean mUidCached;
private boolean mProcessInstrumented; private boolean mProcessInstrumented;
private boolean mProcessPersistent; private boolean mProcessPersistent;
@@ -409,7 +410,8 @@ class BroadcastProcessQueue {
* {@link BroadcastQueueModernImpl#updateRunnableList} * {@link BroadcastQueueModernImpl#updateRunnableList}
*/ */
@CheckResult @CheckResult
public boolean setProcessAndUidCached(@Nullable ProcessRecord app, boolean uidCached) { public boolean setProcessAndUidState(@Nullable ProcessRecord app, boolean uidForeground,
boolean uidCached) {
this.app = app; this.app = app;
// Since we may have just changed our PID, invalidate cached strings // Since we may have just changed our PID, invalidate cached strings
@@ -419,16 +421,34 @@ class BroadcastProcessQueue {
boolean didSomething = false; boolean didSomething = false;
if (app != null) { if (app != null) {
didSomething |= setUidCached(uidCached); didSomething |= setUidCached(uidCached);
didSomething |= setUidForeground(uidForeground);
didSomething |= setProcessInstrumented(app.getActiveInstrumentation() != null); didSomething |= setProcessInstrumented(app.getActiveInstrumentation() != null);
didSomething |= setProcessPersistent(app.isPersistent()); didSomething |= setProcessPersistent(app.isPersistent());
} else { } else {
didSomething |= setUidCached(uidCached); didSomething |= setUidCached(uidCached);
didSomething |= setUidForeground(false);
didSomething |= setProcessInstrumented(false); didSomething |= setProcessInstrumented(false);
didSomething |= setProcessPersistent(false); didSomething |= setProcessPersistent(false);
} }
return didSomething; return didSomething;
} }
/**
* Update if the UID this process is belongs to is in "foreground" state, which signals
* broadcast dispatch should prioritize delivering broadcasts to this process to minimize any
* delays in UI updates.
*/
@CheckResult
private boolean setUidForeground(boolean uidForeground) {
if (mUidForeground != uidForeground) {
mUidForeground = uidForeground;
invalidateRunnableAt();
return true;
} else {
return false;
}
}
/** /**
* Update if this process is in the "cached" state, typically signaling that * Update if this process is in the "cached" state, typically signaling that
* broadcast dispatch should be paused or delayed. * broadcast dispatch should be paused or delayed.
@@ -994,7 +1014,7 @@ class BroadcastProcessQueue {
static final int REASON_CONTAINS_RESULT_TO = 15; static final int REASON_CONTAINS_RESULT_TO = 15;
static final int REASON_CONTAINS_INSTRUMENTED = 16; static final int REASON_CONTAINS_INSTRUMENTED = 16;
static final int REASON_CONTAINS_MANIFEST = 17; static final int REASON_CONTAINS_MANIFEST = 17;
static final int REASON_FOREGROUND_ACTIVITIES = 18; static final int REASON_FOREGROUND = 18;
@IntDef(flag = false, prefix = { "REASON_" }, value = { @IntDef(flag = false, prefix = { "REASON_" }, value = {
REASON_EMPTY, REASON_EMPTY,
@@ -1014,7 +1034,7 @@ class BroadcastProcessQueue {
REASON_CONTAINS_RESULT_TO, REASON_CONTAINS_RESULT_TO,
REASON_CONTAINS_INSTRUMENTED, REASON_CONTAINS_INSTRUMENTED,
REASON_CONTAINS_MANIFEST, REASON_CONTAINS_MANIFEST,
REASON_FOREGROUND_ACTIVITIES, REASON_FOREGROUND,
}) })
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
public @interface Reason {} public @interface Reason {}
@@ -1038,7 +1058,7 @@ class BroadcastProcessQueue {
case REASON_CONTAINS_RESULT_TO: return "CONTAINS_RESULT_TO"; case REASON_CONTAINS_RESULT_TO: return "CONTAINS_RESULT_TO";
case REASON_CONTAINS_INSTRUMENTED: return "CONTAINS_INSTRUMENTED"; case REASON_CONTAINS_INSTRUMENTED: return "CONTAINS_INSTRUMENTED";
case REASON_CONTAINS_MANIFEST: return "CONTAINS_MANIFEST"; case REASON_CONTAINS_MANIFEST: return "CONTAINS_MANIFEST";
case REASON_FOREGROUND_ACTIVITIES: return "FOREGROUND_ACTIVITIES"; case REASON_FOREGROUND: return "FOREGROUND";
default: return Integer.toString(reason); default: return Integer.toString(reason);
} }
} }
@@ -1077,11 +1097,9 @@ class BroadcastProcessQueue {
} else if (mProcessInstrumented) { } else if (mProcessInstrumented) {
mRunnableAt = runnableAt + constants.DELAY_URGENT_MILLIS; mRunnableAt = runnableAt + constants.DELAY_URGENT_MILLIS;
mRunnableAtReason = REASON_INSTRUMENTED; mRunnableAtReason = REASON_INSTRUMENTED;
} else if (app != null && app.hasForegroundActivities()) { } else if (mUidForeground) {
// TODO: Listen for uid state changes to check when an uid goes in and out of
// the TOP state.
mRunnableAt = runnableAt + constants.DELAY_URGENT_MILLIS; mRunnableAt = runnableAt + constants.DELAY_URGENT_MILLIS;
mRunnableAtReason = REASON_FOREGROUND_ACTIVITIES; mRunnableAtReason = REASON_FOREGROUND;
} else if (mCountOrdered > 0) { } else if (mCountOrdered > 0) {
mRunnableAt = runnableAt; mRunnableAt = runnableAt;
mRunnableAtReason = REASON_CONTAINS_ORDERED; mRunnableAtReason = REASON_CONTAINS_ORDERED;
@@ -1351,7 +1369,11 @@ class BroadcastProcessQueue {
@NeverCompile @NeverCompile
private void dumpProcessState(@NonNull IndentingPrintWriter pw) { private void dumpProcessState(@NonNull IndentingPrintWriter pw) {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
if (mUidForeground) {
sb.append("FG");
}
if (mUidCached) { if (mUidCached) {
if (sb.length() > 0) sb.append("|");
sb.append("CACHED"); sb.append("CACHED");
} }
if (mProcessInstrumented) { if (mProcessInstrumented) {

View File

@@ -211,6 +211,17 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
private final AtomicReference<ArraySet<BroadcastRecord>> mReplacedBroadcastsCache = private final AtomicReference<ArraySet<BroadcastRecord>> mReplacedBroadcastsCache =
new AtomicReference<>(); new AtomicReference<>();
/**
* Map from UID to its last known "foreground" state. A UID is considered to be in
* "foreground" state when it's procState is {@link ActivityManager#PROCESS_STATE_TOP}.
* <p>
* We manually maintain this data structure since the lifecycle of
* {@link ProcessRecord} and {@link BroadcastProcessQueue} can be
* mismatched.
*/
@GuardedBy("mService")
private final SparseBooleanArray mUidForeground = new SparseBooleanArray();
/** /**
* Map from UID to its last known "cached" state. * Map from UID to its last known "cached" state.
* <p> * <p>
@@ -1284,11 +1295,24 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
return UserHandle.getUserId(q.uid) == userId; return UserHandle.getUserId(q.uid) == userId;
}; };
broadcastPredicate = BROADCAST_PREDICATE_ANY; broadcastPredicate = BROADCAST_PREDICATE_ANY;
cleanupUserStateLocked(mUidCached, userId);
cleanupUserStateLocked(mUidForeground, userId);
} }
return forEachMatchingBroadcast(queuePredicate, broadcastPredicate, return forEachMatchingBroadcast(queuePredicate, broadcastPredicate,
mBroadcastConsumerSkip, true); mBroadcastConsumerSkip, true);
} }
@GuardedBy("mService")
private void cleanupUserStateLocked(@NonNull SparseBooleanArray uidState, int userId) {
for (int i = uidState.size() - 1; i >= 0; --i) {
final int uid = uidState.keyAt(i);
if (UserHandle.getUserId(uid) == userId) {
uidState.removeAt(i);
}
}
}
private static final Predicate<BroadcastProcessQueue> QUEUE_PREDICATE_ANY = private static final Predicate<BroadcastProcessQueue> QUEUE_PREDICATE_ANY =
(q) -> true; (q) -> true;
private static final BroadcastPredicate BROADCAST_PREDICATE_ANY = private static final BroadcastPredicate BROADCAST_PREDICATE_ANY =
@@ -1403,6 +1427,19 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
mBgConstants.startObserving(mHandler, resolver); mBgConstants.startObserving(mHandler, resolver);
mService.registerUidObserver(new UidObserver() { mService.registerUidObserver(new UidObserver() {
@Override
public void onUidStateChanged(int uid, int procState, long procStateSeq,
int capability) {
synchronized (mService) {
if (procState == ActivityManager.PROCESS_STATE_TOP) {
mUidForeground.put(uid, true);
} else {
mUidForeground.delete(uid);
}
refreshProcessQueuesLocked(uid);
}
}
@Override @Override
public void onUidCachedChanged(int uid, boolean cached) { public void onUidCachedChanged(int uid, boolean cached) {
synchronized (mService) { synchronized (mService) {
@@ -1411,18 +1448,11 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
} else { } else {
mUidCached.delete(uid); mUidCached.delete(uid);
} }
refreshProcessQueuesLocked(uid);
BroadcastProcessQueue leaf = mProcessQueues.get(uid);
while (leaf != null) {
// Update internal state by refreshing values previously
// read from any known running process
setQueueProcess(leaf, leaf.app);
leaf = leaf.processNameNext;
}
enqueueUpdateRunningList();
} }
} }
}, ActivityManager.UID_OBSERVER_CACHED, 0, "android"); }, ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_CACHED,
ActivityManager.PROCESS_STATE_TOP, "android");
// Kick off periodic health checks // Kick off periodic health checks
mLocalHandler.sendEmptyMessage(MSG_CHECK_HEALTH); mLocalHandler.sendEmptyMessage(MSG_CHECK_HEALTH);
@@ -1611,8 +1641,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// warm via this operation, we're going to immediately promote it to // warm via this operation, we're going to immediately promote it to
// be running, and any side effect of this operation will then apply // be running, and any side effect of this operation will then apply
// after it's finished and is returned to the runnable list. // after it's finished and is returned to the runnable list.
queue.setProcessAndUidCached( queue.setProcessAndUidState(
mService.getProcessRecordLocked(queue.processName, queue.uid), mService.getProcessRecordLocked(queue.processName, queue.uid),
mUidForeground.get(queue.uid, false),
mUidCached.get(queue.uid, false)); mUidCached.get(queue.uid, false));
} }
} }
@@ -1624,11 +1655,28 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
*/ */
private void setQueueProcess(@NonNull BroadcastProcessQueue queue, private void setQueueProcess(@NonNull BroadcastProcessQueue queue,
@Nullable ProcessRecord app) { @Nullable ProcessRecord app) {
if (queue.setProcessAndUidCached(app, mUidCached.get(queue.uid, false))) { if (queue.setProcessAndUidState(app, mUidForeground.get(queue.uid, false),
mUidCached.get(queue.uid, false))) {
updateRunnableList(queue); updateRunnableList(queue);
} }
} }
/**
* Refresh the process queues with the latest process state so that runnableAt
* can be updated.
*/
@GuardedBy("mService")
private void refreshProcessQueuesLocked(int uid) {
BroadcastProcessQueue leaf = mProcessQueues.get(uid);
while (leaf != null) {
// Update internal state by refreshing values previously
// read from any known running process
setQueueProcess(leaf, leaf.app);
leaf = leaf.processNameNext;
}
enqueueUpdateRunningList();
}
/** /**
* Inform other parts of OS that the given broadcast queue has started * Inform other parts of OS that the given broadcast queue has started
* running, typically for internal bookkeeping. * running, typically for internal bookkeeping.
@@ -1950,7 +1998,13 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
ipw.println("Cached UIDs:"); ipw.println("Cached UIDs:");
ipw.increaseIndent(); ipw.increaseIndent();
ipw.println(mUidCached.toString()); ipw.println(mUidCached);
ipw.decreaseIndent();
ipw.println();
ipw.println("Foreground UIDs:");
ipw.increaseIndent();
ipw.println(mUidForeground);
ipw.decreaseIndent(); ipw.decreaseIndent();
ipw.println(); ipw.println();

View File

@@ -1072,11 +1072,6 @@ class ProcessRecord implements WindowProcessListener {
return mState.isCached(); return mState.isCached();
} }
@GuardedBy(anyOf = {"mService", "mProcLock"})
public boolean hasForegroundActivities() {
return mState.hasForegroundActivities();
}
boolean hasActivities() { boolean hasActivities() {
return mWindowProcessController.hasActivities(); return mWindowProcessController.hasActivities();
} }

View File

@@ -393,9 +393,9 @@ public final class BroadcastQueueModernImplTest {
List.of(makeMockRegisteredReceiver()), false); List.of(makeMockRegisteredReceiver()), false);
enqueueOrReplaceBroadcast(queue, airplaneRecord, 0); enqueueOrReplaceBroadcast(queue, airplaneRecord, 0);
queue.setProcessAndUidCached(null, false); queue.setProcessAndUidState(null, false, false);
final long notCachedRunnableAt = queue.getRunnableAt(); final long notCachedRunnableAt = queue.getRunnableAt();
queue.setProcessAndUidCached(null, true); queue.setProcessAndUidState(null, false, true);
final long cachedRunnableAt = queue.getRunnableAt(); final long cachedRunnableAt = queue.getRunnableAt();
assertThat(cachedRunnableAt).isGreaterThan(notCachedRunnableAt); assertThat(cachedRunnableAt).isGreaterThan(notCachedRunnableAt);
assertFalse(queue.isRunnable()); assertFalse(queue.isRunnable());
@@ -420,9 +420,9 @@ public final class BroadcastQueueModernImplTest {
List.of(makeMockRegisteredReceiver()), false); List.of(makeMockRegisteredReceiver()), false);
enqueueOrReplaceBroadcast(queue, airplaneRecord, 0); enqueueOrReplaceBroadcast(queue, airplaneRecord, 0);
queue.setProcessAndUidCached(null, false); queue.setProcessAndUidState(null, false, false);
final long notCachedRunnableAt = queue.getRunnableAt(); final long notCachedRunnableAt = queue.getRunnableAt();
queue.setProcessAndUidCached(null, true); queue.setProcessAndUidState(null, false, true);
final long cachedRunnableAt = queue.getRunnableAt(); final long cachedRunnableAt = queue.getRunnableAt();
assertThat(cachedRunnableAt).isGreaterThan(notCachedRunnableAt); assertThat(cachedRunnableAt).isGreaterThan(notCachedRunnableAt);
assertTrue(queue.isRunnable()); assertTrue(queue.isRunnable());
@@ -452,13 +452,13 @@ public final class BroadcastQueueModernImplTest {
// verify that: // verify that:
// (a) the queue is immediately runnable by existence of a fg-priority broadcast // (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 // (b) the next one up is the fg-priority broadcast despite its later enqueue time
queue.setProcessAndUidCached(null, false); queue.setProcessAndUidState(null, false, false);
assertTrue(queue.isRunnable()); assertTrue(queue.isRunnable());
assertThat(queue.getRunnableAt()).isAtMost(airplaneRecord.enqueueClockTime); assertThat(queue.getRunnableAt()).isAtMost(airplaneRecord.enqueueClockTime);
assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked()); assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked());
assertEquals(queue.peekNextBroadcastRecord(), airplaneRecord); assertEquals(queue.peekNextBroadcastRecord(), airplaneRecord);
queue.setProcessAndUidCached(null, true); queue.setProcessAndUidState(null, false, true);
assertTrue(queue.isRunnable()); assertTrue(queue.isRunnable());
assertThat(queue.getRunnableAt()).isAtMost(airplaneRecord.enqueueClockTime); assertThat(queue.getRunnableAt()).isAtMost(airplaneRecord.enqueueClockTime);
assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked()); assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked());
@@ -515,6 +515,28 @@ public final class BroadcastQueueModernImplTest {
assertEquals(BroadcastProcessQueue.REASON_MAX_PENDING, queue.getRunnableAtReason()); assertEquals(BroadcastProcessQueue.REASON_MAX_PENDING, queue.getRunnableAtReason());
} }
@Test
public void testRunnableAt_uidForeground() {
final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants, PACKAGE_GREEN,
getUidForPackage(PACKAGE_GREEN));
final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
final BroadcastRecord timeTickRecord = makeBroadcastRecord(timeTick,
List.of(makeMockRegisteredReceiver()));
enqueueOrReplaceBroadcast(queue, timeTickRecord, 0);
assertThat(queue.getRunnableAt()).isGreaterThan(timeTickRecord.enqueueTime);
assertEquals(BroadcastProcessQueue.REASON_NORMAL, queue.getRunnableAtReason());
queue.setProcessAndUidState(mProcess, true, false);
assertThat(queue.getRunnableAt()).isLessThan(timeTickRecord.enqueueTime);
assertEquals(BroadcastProcessQueue.REASON_FOREGROUND, queue.getRunnableAtReason());
queue.setProcessAndUidState(mProcess, false, false);
assertThat(queue.getRunnableAt()).isGreaterThan(timeTickRecord.enqueueTime);
assertEquals(BroadcastProcessQueue.REASON_NORMAL, queue.getRunnableAtReason());
}
/** /**
* Verify that a cached process that would normally be delayed becomes * Verify that a cached process that would normally be delayed becomes
* immediately runnable when the given broadcast is enqueued. * immediately runnable when the given broadcast is enqueued.
@@ -522,7 +544,7 @@ public final class BroadcastQueueModernImplTest {
private void doRunnableAt_Cached(BroadcastRecord testRecord, int testRunnableAtReason) { private void doRunnableAt_Cached(BroadcastRecord testRecord, int testRunnableAtReason) {
final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants, final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN)); PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
queue.setProcessAndUidCached(null, true); queue.setProcessAndUidState(null, false, true);
final BroadcastRecord lazyRecord = makeBroadcastRecord( final BroadcastRecord lazyRecord = makeBroadcastRecord(
new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED),

View File

@@ -25,12 +25,15 @@ import static com.android.server.am.BroadcastProcessQueue.reasonToString;
import static com.android.server.am.BroadcastRecord.deliveryStateToString; import static com.android.server.am.BroadcastRecord.deliveryStateToString;
import static com.android.server.am.BroadcastRecord.isReceiverEquals; import static com.android.server.am.BroadcastRecord.isReceiverEquals;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
@@ -2132,4 +2135,75 @@ public class BroadcastQueueTest {
waitForIdle(); waitForIdle();
verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, airplane); verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, airplane);
} }
@Test
public void testBroadcastDelivery_uidForeground() throws Exception {
// Legacy stack doesn't support prioritization to foreground app.
Assume.assumeTrue(mImpl == Impl.MODERN);
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
mUidObserver.onUidStateChanged(receiverGreenApp.info.uid,
ActivityManager.PROCESS_STATE_TOP, 0, ActivityManager.PROCESS_CAPABILITY_NONE);
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
final BroadcastFilter receiverBlue = makeRegisteredReceiver(receiverBlueApp);
final BroadcastFilter receiverGreen = makeRegisteredReceiver(receiverGreenApp);
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, callerApp,
List.of(receiverBlue));
final BroadcastRecord timeTickRecord = makeBroadcastRecord(timeTick, callerApp,
List.of(receiverBlue, receiverGreen));
enqueueBroadcast(airplaneRecord);
enqueueBroadcast(timeTickRecord);
waitForIdle();
// Verify that broadcasts to receiverGreenApp gets scheduled first.
assertThat(getReceiverScheduledTime(timeTickRecord, receiverGreen))
.isLessThan(getReceiverScheduledTime(airplaneRecord, receiverBlue));
assertThat(getReceiverScheduledTime(timeTickRecord, receiverGreen))
.isLessThan(getReceiverScheduledTime(timeTickRecord, receiverBlue));
}
@Test
public void testPrioritizedBroadcastDelivery_uidForeground() throws Exception {
// Legacy stack doesn't support prioritization to foreground app.
Assume.assumeTrue(mImpl == Impl.MODERN);
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
mUidObserver.onUidStateChanged(receiverGreenApp.info.uid,
ActivityManager.PROCESS_STATE_TOP, 0, ActivityManager.PROCESS_CAPABILITY_NONE);
final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
final BroadcastFilter receiverBlue = makeRegisteredReceiver(receiverBlueApp, 10);
final BroadcastFilter receiverGreen = makeRegisteredReceiver(receiverGreenApp, 5);
final BroadcastRecord prioritizedRecord = makeBroadcastRecord(timeTick, callerApp,
List.of(receiverBlue, receiverGreen));
enqueueBroadcast(prioritizedRecord);
waitForIdle();
// Verify that uid foreground-ness does not impact that delivery of prioritized broadcast.
// That is, broadcast to receiverBlueApp gets scheduled before the one to receiverGreenApp.
assertThat(getReceiverScheduledTime(prioritizedRecord, receiverGreen))
.isGreaterThan(getReceiverScheduledTime(prioritizedRecord, receiverBlue));
}
private long getReceiverScheduledTime(@NonNull BroadcastRecord r, @NonNull Object receiver) {
for (int i = 0; i < r.receivers.size(); ++i) {
if (isReceiverEquals(receiver, r.receivers.get(i))) {
return r.scheduledTime[i];
}
}
fail(receiver + "not found in " + r);
return -1;
}
} }