summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Sudheer Shanka <sudheersai@google.com> 2023-04-05 23:16:20 +0000
committer Sudheer Shanka <sudheersai@google.com> 2023-05-09 06:34:17 +0000
commitda00d72c176d556c2a1138db37594990853d445c (patch)
treea2636c91a80d6d0e0275cc9e06c09d1e3e446f12
parent2942f9a42d966ad09faa7f104a20efac78fb1d7b (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
-rw-r--r--services/core/java/com/android/server/am/BroadcastProcessQueue.java38
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueModernImpl.java80
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java5
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java36
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java74
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;
+ }
}