Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 827e4168 authored by Sudheer Shanka's avatar Sudheer Shanka Committed by Automerger Merge Worker
Browse files

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

Merge "Add a callback to listen to uid foreground-ness change." into udc-dev am: b0302b10 am: 179cad08

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/22499498



Change-Id: I918670931bd095b161fe39bbb1b19cf62a81acd4
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents c179bb00 179cad08
Loading
Loading
Loading
Loading
+30 −8
Original line number Diff line number Diff line
@@ -200,6 +200,7 @@ class BroadcastProcessQueue {
     */
    private boolean mLastDeferredStates;

    private boolean mUidForeground;
    private boolean mUidCached;
    private boolean mProcessInstrumented;
    private boolean mProcessPersistent;
@@ -409,7 +410,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
@@ -419,16 +421,34 @@ 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);
        }
        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
     * 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_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,
@@ -1014,7 +1034,7 @@ class BroadcastProcessQueue {
            REASON_CONTAINS_RESULT_TO,
            REASON_CONTAINS_INSTRUMENTED,
            REASON_CONTAINS_MANIFEST,
            REASON_FOREGROUND_ACTIVITIES,
            REASON_FOREGROUND,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface Reason {}
@@ -1038,7 +1058,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);
        }
    }
@@ -1077,11 +1097,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;
@@ -1351,7 +1369,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) {
+67 −13
Original line number Diff line number Diff line
@@ -211,6 +211,17 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
    private final AtomicReference<ArraySet<BroadcastRecord>> mReplacedBroadcastsCache =
            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>
@@ -1284,11 +1295,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 =
@@ -1403,6 +1427,19 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
        mBgConstants.startObserving(mHandler, resolver);

        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) {
@@ -1411,18 +1448,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);
@@ -1611,8 +1641,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));
        }
    }
@@ -1624,11 +1655,28 @@ 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.
@@ -1950,7 +1998,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();

+0 −5
Original line number Diff line number Diff line
@@ -1072,11 +1072,6 @@ class ProcessRecord implements WindowProcessListener {
        return mState.isCached();
    }

    @GuardedBy(anyOf = {"mService", "mProcLock"})
    public boolean hasForegroundActivities() {
        return mState.hasForegroundActivities();
    }

    boolean hasActivities() {
        return mWindowProcessController.hasActivities();
    }
+29 −7
Original line number Diff line number Diff line
@@ -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),
+74 −0
Original line number Diff line number Diff line
@@ -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;
@@ -2132,4 +2135,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;
    }
}