diff options
| author | 2023-04-05 23:16:20 +0000 | |
|---|---|---|
| committer | 2023-05-09 06:34:17 +0000 | |
| commit | da00d72c176d556c2a1138db37594990853d445c (patch) | |
| tree | a2636c91a80d6d0e0275cc9e06c09d1e3e446f12 | |
| parent | 2942f9a42d966ad09faa7f104a20efac78fb1d7b (diff) | |
Add a callback to listen to uid foreground-ness change.
Bug: 276941957
Test: atest services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
Test: atest services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
Change-Id: Id11bb1c06025bf758a6c6b40f538ed45548381e8
5 files changed, 200 insertions, 33 deletions
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index 5c68e6759083..e73153342db7 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -194,6 +194,7 @@ class BroadcastProcessQueue { */ private boolean mLastDeferredStates; + private boolean mUidForeground; private boolean mUidCached; private boolean mProcessInstrumented; private boolean mProcessPersistent; @@ -403,7 +404,8 @@ class BroadcastProcessQueue { * {@link BroadcastQueueModernImpl#updateRunnableList} */ @CheckResult - public boolean setProcessAndUidCached(@Nullable ProcessRecord app, boolean uidCached) { + public boolean setProcessAndUidState(@Nullable ProcessRecord app, boolean uidForeground, + boolean uidCached) { this.app = app; // Since we may have just changed our PID, invalidate cached strings @@ -413,10 +415,12 @@ class BroadcastProcessQueue { boolean didSomething = false; if (app != null) { didSomething |= setUidCached(uidCached); + didSomething |= setUidForeground(uidForeground); didSomething |= setProcessInstrumented(app.getActiveInstrumentation() != null); didSomething |= setProcessPersistent(app.isPersistent()); } else { didSomething |= setUidCached(uidCached); + didSomething |= setUidForeground(false); didSomething |= setProcessInstrumented(false); didSomething |= setProcessPersistent(false); } @@ -424,6 +428,22 @@ class BroadcastProcessQueue { } /** + * 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 * broadcast dispatch should be paused or delayed. */ @@ -917,7 +937,7 @@ class BroadcastProcessQueue { static final int REASON_CONTAINS_RESULT_TO = 15; static final int REASON_CONTAINS_INSTRUMENTED = 16; 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 = { REASON_EMPTY, @@ -937,7 +957,7 @@ class BroadcastProcessQueue { REASON_CONTAINS_RESULT_TO, REASON_CONTAINS_INSTRUMENTED, REASON_CONTAINS_MANIFEST, - REASON_FOREGROUND_ACTIVITIES, + REASON_FOREGROUND, }) @Retention(RetentionPolicy.SOURCE) public @interface Reason {} @@ -961,7 +981,7 @@ class BroadcastProcessQueue { case REASON_CONTAINS_RESULT_TO: return "CONTAINS_RESULT_TO"; case REASON_CONTAINS_INSTRUMENTED: return "CONTAINS_INSTRUMENTED"; case REASON_CONTAINS_MANIFEST: return "CONTAINS_MANIFEST"; - case REASON_FOREGROUND_ACTIVITIES: return "FOREGROUND_ACTIVITIES"; + case REASON_FOREGROUND: return "FOREGROUND"; default: return Integer.toString(reason); } } @@ -1000,11 +1020,9 @@ class BroadcastProcessQueue { } else if (mProcessInstrumented) { mRunnableAt = runnableAt + constants.DELAY_URGENT_MILLIS; mRunnableAtReason = REASON_INSTRUMENTED; - } else if (app != null && app.hasForegroundActivities()) { - // TODO: Listen for uid state changes to check when an uid goes in and out of - // the TOP state. + } else if (mUidForeground) { mRunnableAt = runnableAt + constants.DELAY_URGENT_MILLIS; - mRunnableAtReason = REASON_FOREGROUND_ACTIVITIES; + mRunnableAtReason = REASON_FOREGROUND; } else if (mCountOrdered > 0) { mRunnableAt = runnableAt; mRunnableAtReason = REASON_CONTAINS_ORDERED; @@ -1274,7 +1292,11 @@ class BroadcastProcessQueue { @NeverCompile private void dumpProcessState(@NonNull IndentingPrintWriter pw) { final StringBuilder sb = new StringBuilder(); + if (mUidForeground) { + sb.append("FG"); + } if (mUidCached) { + if (sb.length() > 0) sb.append("|"); sb.append("CACHED"); } if (mProcessInstrumented) { diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index 96e152320282..9d36d4289ad1 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -211,6 +211,17 @@ class BroadcastQueueModernImpl extends BroadcastQueue { 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. * <p> * We manually maintain this data structure since the lifecycle of @@ -1212,11 +1223,24 @@ class BroadcastQueueModernImpl extends BroadcastQueue { return UserHandle.getUserId(q.uid) == userId; }; broadcastPredicate = BROADCAST_PREDICATE_ANY; + + cleanupUserStateLocked(mUidCached, userId); + cleanupUserStateLocked(mUidForeground, userId); } return forEachMatchingBroadcast(queuePredicate, broadcastPredicate, 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 = (q) -> true; private static final BroadcastPredicate BROADCAST_PREDICATE_ANY = @@ -1332,6 +1356,19 @@ class BroadcastQueueModernImpl extends BroadcastQueue { 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 public void onUidCachedChanged(int uid, boolean cached) { synchronized (mService) { if (cached) { @@ -1339,18 +1376,11 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } 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 - setQueueProcess(leaf, leaf.app); - leaf = leaf.processNameNext; - } - enqueueUpdateRunningList(); + refreshProcessQueuesLocked(uid); } } - }, ActivityManager.UID_OBSERVER_CACHED, 0, "android"); + }, ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_CACHED, + ActivityManager.PROCESS_STATE_TOP, "android"); // Kick off periodic health checks mLocalHandler.sendEmptyMessage(MSG_CHECK_HEALTH); @@ -1522,8 +1552,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.setProcessAndUidCached( + queue.setProcessAndUidState( mService.getProcessRecordLocked(queue.processName, queue.uid), + mUidForeground.get(queue.uid, false), mUidCached.get(queue.uid, false)); } } @@ -1535,12 +1566,29 @@ class BroadcastQueueModernImpl extends BroadcastQueue { */ private void setQueueProcess(@NonNull BroadcastProcessQueue queue, @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); } } /** + * 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 * running, typically for internal bookkeeping. */ @@ -1861,7 +1909,13 @@ class BroadcastQueueModernImpl extends BroadcastQueue { ipw.println("Cached UIDs:"); 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.println(); diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 438a08c44ef4..ca160b70d707 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -1057,11 +1057,6 @@ class ProcessRecord implements WindowProcessListener { return mState.isCached(); } - @GuardedBy(anyOf = {"mService", "mProcLock"}) - public boolean hasForegroundActivities() { - return mState.hasForegroundActivities(); - } - boolean hasActivities() { return mWindowProcessController.hasActivities(); } 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 acfea85d60a2..626c6dc7280e 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.setProcessAndUidCached(null, false); + queue.setProcessAndUidState(null, false, false); final long notCachedRunnableAt = queue.getRunnableAt(); - queue.setProcessAndUidCached(null, true); + queue.setProcessAndUidState(null, 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.setProcessAndUidCached(null, false); + queue.setProcessAndUidState(null, false, false); final long notCachedRunnableAt = queue.getRunnableAt(); - queue.setProcessAndUidCached(null, true); + queue.setProcessAndUidState(null, false, true); final long cachedRunnableAt = queue.getRunnableAt(); assertThat(cachedRunnableAt).isGreaterThan(notCachedRunnableAt); assertTrue(queue.isRunnable()); @@ -452,13 +452,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.setProcessAndUidCached(null, false); + queue.setProcessAndUidState(null, false, false); assertTrue(queue.isRunnable()); assertThat(queue.getRunnableAt()).isAtMost(airplaneRecord.enqueueClockTime); assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked()); assertEquals(queue.peekNextBroadcastRecord(), airplaneRecord); - queue.setProcessAndUidCached(null, true); + queue.setProcessAndUidState(null, false, true); assertTrue(queue.isRunnable()); assertThat(queue.getRunnableAt()).isAtMost(airplaneRecord.enqueueClockTime); assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked()); @@ -515,6 +515,28 @@ public final class BroadcastQueueModernImplTest { 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 * immediately runnable when the given broadcast is enqueued. @@ -522,7 +544,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.setProcessAndUidCached(null, true); + queue.setProcessAndUidState(null, false, true); final BroadcastRecord lazyRecord = makeBroadcastRecord( new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), 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 7be1d7cde27f..bc6f5029ea17 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -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.isReceiverEquals; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -2102,4 +2105,75 @@ public class BroadcastQueueTest { waitForIdle(); 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; + } } |