diff options
9 files changed, 244 insertions, 23 deletions
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 5d3bb31bc31f..a31015ff7aa2 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -18964,6 +18964,13 @@ public class ActivityManagerService extends IActivityManager.Stub pw.flush(); } + void waitForBroadcastDispatch(@NonNull PrintWriter pw, @NonNull Intent intent) { + enforceCallingPermission(permission.DUMP, "waitForBroadcastDispatch"); + for (BroadcastQueue queue : mBroadcastQueues) { + queue.waitForDispatched(intent, pw); + } + } + void setIgnoreDeliveryGroupPolicy(@NonNull String broadcastAction) { Objects.requireNonNull(broadcastAction); enforceCallingPermission(permission.DUMP, "waitForBroadcastBarrier()"); diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 17a0d62c27b3..8759e3f207c4 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -368,6 +368,8 @@ final class ActivityManagerShellCommand extends ShellCommand { return runWaitForBroadcastBarrier(pw); case "wait-for-application-barrier": return runWaitForApplicationBarrier(pw); + case "wait-for-broadcast-dispatch": + return runWaitForBroadcastDispatch(pw); case "set-ignore-delivery-group-policy": return runSetIgnoreDeliveryGroupPolicy(pw); case "clear-ignore-delivery-group-policy": @@ -3472,6 +3474,18 @@ final class ActivityManagerShellCommand extends ShellCommand { return 0; } + int runWaitForBroadcastDispatch(PrintWriter pw) throws RemoteException { + pw = new PrintWriter(new TeeWriter(LOG_WRITER_INFO, pw)); + final Intent intent; + try { + intent = makeIntent(UserHandle.USER_CURRENT); + } catch (URISyntaxException e) { + throw new RuntimeException(e.getMessage(), e); + } + mInternal.waitForBroadcastDispatch(pw, intent); + return 0; + } + int runSetIgnoreDeliveryGroupPolicy(PrintWriter pw) throws RemoteException { final String broadcastAction = getNextArgRequired(); mInternal.setIgnoreDeliveryGroupPolicy(broadcastAction); diff --git a/services/core/java/com/android/server/am/BroadcastDispatcher.java b/services/core/java/com/android/server/am/BroadcastDispatcher.java index 2adcf2f48343..8aa3921d3f2f 100644 --- a/services/core/java/com/android/server/am/BroadcastDispatcher.java +++ b/services/core/java/com/android/server/am/BroadcastDispatcher.java @@ -582,6 +582,38 @@ public class BroadcastDispatcher { } } + private static boolean isDispatchedInDeferrals(@NonNull ArrayList<Deferrals> list, + @NonNull Intent intent) { + for (int i = 0; i < list.size(); i++) { + if (!isDispatched(list.get(i).broadcasts, intent)) { + return false; + } + } + return true; + } + + private static boolean isDispatched(@NonNull ArrayList<BroadcastRecord> list, + @NonNull Intent intent) { + for (int i = 0; i < list.size(); i++) { + if (intent.filterEquals(list.get(i).intent)) { + return false; + } + } + return true; + } + + public boolean isDispatched(@NonNull Intent intent) { + synchronized (mLock) { + if ((mCurrentBroadcast != null) && intent.filterEquals(mCurrentBroadcast.intent)) { + return false; + } + return isDispatched(mOrderedBroadcasts, intent) + && isDispatched(mAlarmQueue, intent) + && isDispatchedInDeferrals(mDeferredBroadcasts, intent) + && isDispatchedInDeferrals(mAlarmDeferrals, intent); + } + } + private static int pendingInDeferralsList(ArrayList<Deferrals> list) { int pending = 0; final int numEntries = list.size(); diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index 5c68e6759083..c7ef8fab2da6 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -182,7 +182,7 @@ class BroadcastProcessQueue { private int mCountInstrumented; private int mCountManifest; - private boolean mPrioritizeEarliest; + private int mCountPrioritizeEarliestRequests; private @UptimeMillisLong long mRunnableAt = Long.MAX_VALUE; private @Reason int mRunnableAtReason = REASON_EMPTY; @@ -748,7 +748,7 @@ class BroadcastProcessQueue { final BroadcastRecord nextLPRecord = (BroadcastRecord) nextLPArgs.arg1; final int nextLPRecordIndex = nextLPArgs.argi1; final BroadcastRecord nextHPRecord = (BroadcastRecord) highPriorityQueue.peekFirst().arg1; - final boolean shouldConsiderLPQueue = (mPrioritizeEarliest + final boolean shouldConsiderLPQueue = (mCountPrioritizeEarliestRequests > 0 || consecutiveHighPriorityCount >= maxHighPriorityDispatchLimit); final boolean isLPQueueEligible = shouldConsiderLPQueue && nextLPRecord.enqueueTime <= nextHPRecord.enqueueTime @@ -761,10 +761,9 @@ class BroadcastProcessQueue { } /** - * When {@code prioritizeEarliest} is set to {@code true}, then earliest enqueued - * broadcasts would be prioritized for dispatching, even if there are urgent broadcasts - * waiting. This is typically used in case there are callers waiting for "barrier" to be - * reached. + * Add a request to prioritize dispatching of broadcasts that have been enqueued the earliest, + * even if there are urgent broadcasts waiting to be dispatched. This is typically used in + * case there are callers waiting for "barrier" to be reached. * * @return if this operation may have changed internal state, indicating * that the caller is responsible for invoking @@ -772,12 +771,38 @@ class BroadcastProcessQueue { */ @CheckResult @VisibleForTesting - boolean setPrioritizeEarliest(boolean prioritizeEarliest) { - if (mPrioritizeEarliest != prioritizeEarliest) { - mPrioritizeEarliest = prioritizeEarliest; + boolean addPrioritizeEarliestRequest() { + if (mCountPrioritizeEarliestRequests == 0) { + mCountPrioritizeEarliestRequests++; invalidateRunnableAt(); return true; } else { + mCountPrioritizeEarliestRequests++; + return false; + } + } + + /** + * Remove a request to prioritize dispatching of broadcasts that have been enqueued the + * earliest, even if there are urgent broadcasts waiting to be dispatched. This is typically + * used in case there are callers waiting for "barrier" to be reached. + * + * <p> Once there are no more remaining requests, the dispatching order reverts back to normal. + * + * @return if this operation may have changed internal state, indicating + * that the caller is responsible for invoking + * {@link BroadcastQueueModernImpl#updateRunnableList} + */ + @CheckResult + boolean removePrioritizeEarliestRequest() { + mCountPrioritizeEarliestRequests--; + if (mCountPrioritizeEarliestRequests == 0) { + invalidateRunnableAt(); + return true; + } else if (mCountPrioritizeEarliestRequests < 0) { + mCountPrioritizeEarliestRequests = 0; + return false; + } else { return false; } } @@ -837,7 +862,7 @@ class BroadcastProcessQueue { } /** - * Quickly determine if this queue has broadcasts enqueued before the given + * Quickly determine if this queue has non-deferred broadcasts enqueued before the given * barrier timestamp that are still waiting to be delivered. */ public boolean isBeyondBarrierLocked(@UptimeMillisLong long barrierTime) { @@ -859,6 +884,41 @@ class BroadcastProcessQueue { || isDeferredUntilActive(); } + /** + * Quickly determine if this queue has non-deferred broadcasts waiting to be dispatched, + * that match {@code intent}, as defined by {@link Intent#filterEquals(Intent)}. + */ + public boolean isDispatched(@NonNull Intent intent) { + final boolean activeDispatched = (mActive == null) + || (!intent.filterEquals(mActive.intent)); + final boolean dispatched = isDispatchedInQueue(mPending, intent); + final boolean urgentDispatched = isDispatchedInQueue(mPendingUrgent, intent); + final boolean offloadDispatched = isDispatchedInQueue(mPendingOffload, intent); + + return (activeDispatched && dispatched && urgentDispatched && offloadDispatched) + || isDeferredUntilActive(); + } + + /** + * Quickly determine if the {@code queue} has non-deferred broadcasts waiting to be dispatched, + * that match {@code intent}, as defined by {@link Intent#filterEquals(Intent)}. + */ + private boolean isDispatchedInQueue(@NonNull ArrayDeque<SomeArgs> queue, + @NonNull Intent intent) { + final Iterator<SomeArgs> it = queue.iterator(); + while (it.hasNext()) { + final SomeArgs args = it.next(); + if (args == null) { + return true; + } + final BroadcastRecord record = (BroadcastRecord) args.arg1; + if (intent.filterEquals(record.intent)) { + return false; + } + } + return true; + } + public boolean isRunnable() { if (mRunnableAtInvalidated) updateRunnableAt(); return mRunnableAt != Long.MAX_VALUE; diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 6d1344d79b6c..8e76e5b5cf48 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -192,7 +192,7 @@ public abstract class BroadcastQueue { public abstract boolean isIdleLocked(); /** - * Quickly determine if this queue has broadcasts enqueued before the given + * Quickly determine if this queue has non-deferred broadcasts enqueued before the given * barrier timestamp that are still waiting to be delivered. * * @see #waitForIdle @@ -202,6 +202,15 @@ public abstract class BroadcastQueue { public abstract boolean isBeyondBarrierLocked(@UptimeMillisLong long barrierTime); /** + * Quickly determine if this queue has non-deferred broadcasts waiting to be dispatched, + * that match {@code intent}, as defined by {@link Intent#filterEquals(Intent)}. + * + * @see #waitForDispatched(Intent, PrintWriter) + */ + @GuardedBy("mService") + public abstract boolean isDispatchedLocked(@NonNull Intent intent); + + /** * Wait until this queue becomes completely idle. * <p> * Any broadcasts waiting to be delivered at some point in the future will @@ -214,7 +223,7 @@ public abstract class BroadcastQueue { public abstract void waitForIdle(@NonNull PrintWriter pw); /** - * Wait until any currently waiting broadcasts have been dispatched. + * Wait until any currently waiting non-deferred broadcasts have been dispatched. * <p> * Any broadcasts waiting to be delivered at some point in the future will * be dispatched as quickly as possible. @@ -225,6 +234,15 @@ public abstract class BroadcastQueue { public abstract void waitForBarrier(@NonNull PrintWriter pw); /** + * Wait until all non-deferred broadcasts matching {@code intent}, as defined by + * {@link Intent#filterEquals(Intent)}, have been dispatched. + * <p> + * Any broadcasts waiting to be delivered at some point in the future will + * be dispatched as quickly as possible. + */ + public abstract void waitForDispatched(@NonNull Intent intent, @NonNull PrintWriter pw); + + /** * Delays delivering broadcasts to the specified package. * * <p> Note that this is only valid for modern queue. diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java index 4a69f90d9fc0..7f3ceb578891 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java @@ -1793,6 +1793,23 @@ public class BroadcastQueueImpl extends BroadcastQueue { return mDispatcher.isBeyondBarrier(barrierTime); } + public boolean isDispatchedLocked(Intent intent) { + if (isIdleLocked()) return true; + + for (int i = 0; i < mParallelBroadcasts.size(); i++) { + if (intent.filterEquals(mParallelBroadcasts.get(i).intent)) { + return false; + } + } + + final BroadcastRecord pending = getPendingBroadcastLocked(); + if ((pending != null) && intent.filterEquals(pending.intent)) { + return false; + } + + return mDispatcher.isDispatched(intent); + } + public void waitForIdle(PrintWriter pw) { waitFor(() -> isIdleLocked(), pw, "idle"); } @@ -1802,6 +1819,10 @@ public class BroadcastQueueImpl extends BroadcastQueue { waitFor(() -> isBeyondBarrierLocked(barrierTime), pw, "barrier"); } + public void waitForDispatched(Intent intent, PrintWriter pw) { + waitFor(() -> isDispatchedLocked(intent), pw, "dispatch"); + } + private void waitFor(BooleanSupplier condition, PrintWriter pw, String conditionName) { long lastPrint = 0; while (true) { diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index 96e152320282..539f4e844413 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -1376,6 +1376,16 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } @Override + public boolean isDispatchedLocked(@NonNull Intent intent) { + return isDispatchedLocked(intent, LOG_WRITER_INFO); + } + + public boolean isDispatchedLocked(@NonNull Intent intent, @NonNull PrintWriter pw) { + return testAllProcessQueues(q -> q.isDispatched(intent), + "dispatch of " + intent, pw); + } + + @Override public void waitForIdle(@NonNull PrintWriter pw) { waitFor(() -> isIdleLocked(pw)); } @@ -1383,28 +1393,35 @@ class BroadcastQueueModernImpl extends BroadcastQueue { @Override public void waitForBarrier(@NonNull PrintWriter pw) { final long now = SystemClock.uptimeMillis(); - waitFor(() -> isBeyondBarrierLocked(now, pw)); + synchronized (mService) { + forEachMatchingQueue(QUEUE_PREDICATE_ANY, + q -> q.addPrioritizeEarliestRequest()); + } + try { + waitFor(() -> isBeyondBarrierLocked(now, pw)); + } finally { + synchronized (mService) { + forEachMatchingQueue(QUEUE_PREDICATE_ANY, + q -> q.removePrioritizeEarliestRequest()); + } + } + } + + @Override + public void waitForDispatched(@NonNull Intent intent, @NonNull PrintWriter pw) { + waitFor(() -> isDispatchedLocked(intent, pw)); } private void waitFor(@NonNull BooleanSupplier condition) { final CountDownLatch latch = new CountDownLatch(1); synchronized (mService) { mWaitingFor.add(Pair.create(condition, latch)); - forEachMatchingQueue(QUEUE_PREDICATE_ANY, - (q) -> q.setPrioritizeEarliest(true)); } enqueueUpdateRunningList(); try { latch.await(); } catch (InterruptedException e) { throw new RuntimeException(e); - } finally { - synchronized (mService) { - if (mWaitingFor.isEmpty()) { - forEachMatchingQueue(QUEUE_PREDICATE_ANY, - (q) -> q.setPrioritizeEarliest(false)); - } - } } } 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..5d3b91368dcb 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java @@ -769,7 +769,7 @@ public final class BroadcastQueueModernImplTest { BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants, PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN)); - queue.setPrioritizeEarliest(true); + queue.addPrioritizeEarliestRequest(); long timeCounter = 100; enqueueOrReplaceBroadcast(queue, @@ -814,6 +814,28 @@ public final class BroadcastQueueModernImplTest { queue.makeActiveNextPending(); assertEquals(AppWidgetManager.ACTION_APPWIDGET_UPDATE, queue.getActive().intent.getAction()); + + + queue.removePrioritizeEarliestRequest(); + + enqueueOrReplaceBroadcast(queue, + makeBroadcastRecord(new Intent(Intent.ACTION_BOOT_COMPLETED) + .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, timeCounter++); + enqueueOrReplaceBroadcast(queue, + makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), + 0, timeCounter++); + enqueueOrReplaceBroadcast(queue, + makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, timeCounter++); + + // Once the request to prioritize earliest is removed, we should expect broadcasts + // to be dispatched in the order of foreground, normal and then offload. + queue.makeActiveNextPending(); + assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction()); + queue.makeActiveNextPending(); + assertEquals(Intent.ACTION_TIMEZONE_CHANGED, queue.getActive().intent.getAction()); + queue.makeActiveNextPending(); + assertEquals(Intent.ACTION_BOOT_COMPLETED, queue.getActive().intent.getAction()); } /** 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..3a8d2c92eaff 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -1890,6 +1890,36 @@ public class BroadcastQueueTest { assertTrue(mQueue.isBeyondBarrierLocked(afterSecond)); } + @Test + public void testWaitForBroadcastDispatch() throws Exception { + final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); + final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN); + + final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK); + assertTrue(mQueue.isDispatchedLocked(timeTick)); + + final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED); + enqueueBroadcast(makeBroadcastRecord(timezone, callerApp, + List.of(makeRegisteredReceiver(receiverApp)))); + + assertTrue(mQueue.isDispatchedLocked(timeTick)); + assertFalse(mQueue.isDispatchedLocked(timezone)); + + enqueueBroadcast(makeBroadcastRecord(timeTick, callerApp, + List.of(makeRegisteredReceiver(receiverApp)))); + + assertFalse(mQueue.isDispatchedLocked(timeTick)); + assertFalse(mQueue.isDispatchedLocked(timezone)); + + mLooper.release(); + + mQueue.waitForDispatched(timeTick, LOG_WRITER_INFO); + assertTrue(mQueue.isDispatchedLocked(timeTick)); + + mQueue.waitForDispatched(timezone, LOG_WRITER_INFO); + assertTrue(mQueue.isDispatchedLocked(timezone)); + } + /** * Verify that we OOM adjust for manifest receivers. */ |