diff options
6 files changed, 342 insertions, 119 deletions
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index bfc8251d97bb..9e61ce405ca7 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -18,7 +18,6 @@ package com.android.server.am; import static com.android.internal.util.Preconditions.checkState; import static com.android.server.am.BroadcastRecord.deliveryStateToString; -import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal; import static com.android.server.am.BroadcastRecord.isReceiverEquals; import android.annotation.IntDef; @@ -709,7 +708,7 @@ class BroadcastProcessQueue { || consecutiveHighPriorityCount >= maxHighPriorityDispatchLimit); final boolean isLPQueueEligible = shouldConsiderLPQueue && nextLPRecord.enqueueTime <= nextHPRecord.enqueueTime - && !blockedOnOrderedDispatch(nextLPRecord, nextLPRecordIndex); + && !nextLPRecord.isBlocked(nextLPRecordIndex); return isLPQueueEligible ? lowPriorityQueue : highPriorityQueue; } @@ -912,39 +911,20 @@ class BroadcastProcessQueue { } } - private boolean blockedOnOrderedDispatch(BroadcastRecord r, int index) { - final int blockedUntilTerminalCount = r.blockedUntilTerminalCount[index]; - - int existingDeferredCount = 0; - if (r.deferUntilActive) { - for (int i = 0; i < index; i++) { - if (r.deferredUntilActive[i]) existingDeferredCount++; - } - } - - // We might be blocked waiting for other receivers to finish, - // typically for an ordered broadcast or priority traunches - if ((r.terminalCount + existingDeferredCount) < blockedUntilTerminalCount - && !isDeliveryStateTerminal(r.getDeliveryState(index))) { - return true; - } - return false; - } - /** * Update {@link #getRunnableAt()} if it's currently invalidated. */ private void updateRunnableAt() { - final SomeArgs next = peekNextBroadcast(); + if (!mRunnableAtInvalidated) return; mRunnableAtInvalidated = false; + + final SomeArgs next = peekNextBroadcast(); if (next != null) { final BroadcastRecord r = (BroadcastRecord) next.arg1; final int index = next.argi1; final long runnableAt = r.enqueueTime; - // If we're specifically queued behind other ordered dispatch activity, - // we aren't runnable yet - if (blockedOnOrderedDispatch(r, index)) { + if (r.isBlocked(index)) { mRunnableAt = Long.MAX_VALUE; mRunnableAtReason = REASON_BLOCKED; return; @@ -1262,12 +1242,12 @@ class BroadcastProcessQueue { pw.print(info.activityInfo.name); } pw.println(); - final int blockedUntilTerminalCount = record.blockedUntilTerminalCount[recordIndex]; - if (blockedUntilTerminalCount != -1) { + final int blockedUntilBeyondCount = record.blockedUntilBeyondCount[recordIndex]; + if (blockedUntilBeyondCount != -1) { pw.print(" blocked until "); - pw.print(blockedUntilTerminalCount); + pw.print(blockedUntilBeyondCount); pw.print(", currently at "); - pw.print(record.terminalCount); + pw.print(record.beyondCount); pw.print(" of "); pw.println(record.receivers.size()); } diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index c2bd84f7e665..e532c15addd0 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -1008,6 +1008,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } final BroadcastRecord r = queue.getActive(); + final int index = queue.getActiveIndex(); if (r.ordered) { r.resultCode = resultCode; r.resultData = resultData; @@ -1015,18 +1016,24 @@ class BroadcastQueueModernImpl extends BroadcastQueue { if (!r.isNoAbort()) { r.resultAbort = resultAbort; } + } - // When the caller aborted an ordered broadcast, we mark all - // remaining receivers as skipped - if (r.resultAbort) { - for (int i = r.terminalCount + 1; i < r.receivers.size(); i++) { - setDeliveryState(null, null, r, i, r.receivers.get(i), - BroadcastRecord.DELIVERY_SKIPPED, "resultAbort"); - } + // To ensure that "beyond" high-water marks are updated in a monotonic + // way, we finish this receiver before possibly skipping any remaining + // aborted receivers + final boolean res = finishReceiverActiveLocked(queue, + BroadcastRecord.DELIVERY_DELIVERED, "remote app"); + + // When the caller aborted an ordered broadcast, we mark all + // remaining receivers as skipped + if (r.resultAbort) { + for (int i = index + 1; i < r.receivers.size(); i++) { + setDeliveryState(null, null, r, i, r.receivers.get(i), + BroadcastRecord.DELIVERY_SKIPPED, "resultAbort"); } } - return finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_DELIVERED, "remote app"); + return res; } /** @@ -1108,21 +1115,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue { @NonNull Object receiver, @DeliveryState int newDeliveryState, @NonNull String reason) { final int cookie = traceBegin("setDeliveryState"); + + // Remember the old state and apply the new state final int oldDeliveryState = getDeliveryState(r, index); - boolean checkFinished = false; - - // Only apply state when we haven't already reached a terminal state; - // this is how we ignore racing timeout messages - if (!isDeliveryStateTerminal(oldDeliveryState)) { - r.setDeliveryState(index, newDeliveryState, reason); - if (oldDeliveryState == BroadcastRecord.DELIVERY_DEFERRED) { - r.deferredCount--; - } else if (newDeliveryState == BroadcastRecord.DELIVERY_DEFERRED) { - // If we're deferring a broadcast, maybe that's enough to unblock the final callback - r.deferredCount++; - checkFinished = true; - } - } + final boolean beyondCountChanged = r.setDeliveryState(index, newDeliveryState, reason); // Emit any relevant tracing results when we're changing the delivery // state as part of running from a queue @@ -1147,15 +1143,13 @@ class BroadcastQueueModernImpl extends BroadcastQueue { + deliveryStateToString(newDeliveryState) + " because " + reason); } - r.terminalCount++; notifyFinishReceiver(queue, app, r, index, receiver); - checkFinished = true; } - // When entire ordered broadcast finished, deliver final result - if (checkFinished) { - final boolean recordFinished = - ((r.terminalCount + r.deferredCount) == r.receivers.size()); - if (recordFinished) { + + // When we've reached a new high-water mark, we might be in a position + // to unblock other receivers or the final resultTo + if (beyondCountChanged) { + if (r.beyondCount == r.receivers.size()) { scheduleResultTo(r); } diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java index c368290386a0..64fe39314f0e 100644 --- a/services/core/java/com/android/server/am/BroadcastRecord.java +++ b/services/core/java/com/android/server/am/BroadcastRecord.java @@ -24,6 +24,7 @@ import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROA import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE; import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_TARGET_T_ONLY; +import android.annotation.CheckResult; import android.annotation.CurrentTimeMillisLong; import android.annotation.ElapsedRealtimeLong; import android.annotation.IntDef; @@ -101,8 +102,7 @@ final class BroadcastRecord extends Binder { final @NonNull List<Object> receivers; // contains BroadcastFilter and ResolveInfo final @DeliveryState int[] delivery; // delivery state of each receiver final @NonNull String[] deliveryReasons; // reasons for delivery state of each receiver - final boolean[] deferredUntilActive; // whether each receiver is infinitely deferred - final int[] blockedUntilTerminalCount; // blocked until count of each receiver + final int[] blockedUntilBeyondCount; // blocked until count of each receiver @Nullable ProcessRecord resultToApp; // who receives final result if non-null @Nullable IIntentReceiver resultTo; // who receives final result if non-null boolean deferred; @@ -134,6 +134,7 @@ final class BroadcastRecord extends Binder { int manifestSkipCount; // number of manifest receivers skipped. int terminalCount; // number of receivers in terminal state. int deferredCount; // number of receivers in deferred state. + int beyondCount; // high-water number of receivers we've moved beyond. @Nullable BroadcastQueue queue; // the outbound queue handling this broadcast // Determines the privileges the app's process has in regard to background starts. @@ -219,6 +220,23 @@ final class BroadcastRecord extends Binder { } } + /** + * Return if the given delivery state is "beyond", which means that we've + * moved beyond this receiver, and future receivers are now unblocked. + */ + static boolean isDeliveryStateBeyond(@DeliveryState int deliveryState) { + switch (deliveryState) { + case DELIVERY_DELIVERED: + case DELIVERY_SKIPPED: + case DELIVERY_TIMEOUT: + case DELIVERY_FAILURE: + case DELIVERY_DEFERRED: + return true; + default: + return false; + } + } + ProcessRecord curApp; // hosting application of current receiver. ComponentName curComponent; // the receiver class that is currently running. ActivityInfo curReceiver; // the manifest receiver that is currently running. @@ -356,7 +374,7 @@ final class BroadcastRecord extends Binder { TimeUtils.formatDuration(terminalTime[i] - scheduledTime[i], pw); pw.print(' '); } - pw.print("("); pw.print(blockedUntilTerminalCount[i]); pw.print(") "); + pw.print("("); pw.print(blockedUntilBeyondCount[i]); pw.print(") "); pw.print("#"); pw.print(i); pw.print(": "); if (o instanceof BroadcastFilter) { pw.println(o); @@ -411,8 +429,7 @@ final class BroadcastRecord extends Binder { urgent = calculateUrgent(_intent, _options); deferUntilActive = calculateDeferUntilActive(_callingUid, _options, _resultTo, _serialized, urgent); - deferredUntilActive = new boolean[deferUntilActive ? delivery.length : 0]; - blockedUntilTerminalCount = calculateBlockedUntilTerminalCount(receivers, _serialized); + blockedUntilBeyondCount = calculateBlockedUntilBeyondCount(receivers, _serialized); scheduledTime = new long[delivery.length]; terminalTime = new long[delivery.length]; resultToApp = _resultToApp; @@ -423,7 +440,7 @@ final class BroadcastRecord extends Binder { ordered = _serialized; sticky = _sticky; initialSticky = _initialSticky; - prioritized = isPrioritized(blockedUntilTerminalCount, _serialized); + prioritized = isPrioritized(blockedUntilBeyondCount, _serialized); userId = _userId; nextReceiver = 0; state = IDLE; @@ -467,8 +484,7 @@ final class BroadcastRecord extends Binder { delivery = from.delivery; deliveryReasons = from.deliveryReasons; deferUntilActive = from.deferUntilActive; - deferredUntilActive = from.deferredUntilActive; - blockedUntilTerminalCount = from.blockedUntilTerminalCount; + blockedUntilBeyondCount = from.blockedUntilBeyondCount; scheduledTime = from.scheduledTime; terminalTime = from.terminalTime; resultToApp = from.resultToApp; @@ -627,32 +643,72 @@ final class BroadcastRecord extends Binder { /** * Update the delivery state of the given {@link #receivers} index. * Automatically updates any time measurements related to state changes. + * + * @return if {@link #beyondCount} changed due to this state transition, + * indicating that other events may be unblocked. */ - void setDeliveryState(int index, @DeliveryState int deliveryState, + @CheckResult + boolean setDeliveryState(int index, @DeliveryState int newDeliveryState, @NonNull String reason) { - delivery[index] = deliveryState; - deliveryReasons[index] = reason; - if (deferUntilActive) deferredUntilActive[index] = false; - switch (deliveryState) { - case DELIVERY_DELIVERED: - case DELIVERY_SKIPPED: - case DELIVERY_TIMEOUT: - case DELIVERY_FAILURE: - terminalTime[index] = SystemClock.uptimeMillis(); + final int oldDeliveryState = delivery[index]; + if (isDeliveryStateTerminal(oldDeliveryState) + || newDeliveryState == oldDeliveryState) { + // We've already arrived in terminal or requested state, so leave + // any statistics and reasons intact from the first transition + return false; + } + + switch (oldDeliveryState) { + case DELIVERY_DEFERRED: + deferredCount--; break; + } + switch (newDeliveryState) { case DELIVERY_SCHEDULED: scheduledTime[index] = SystemClock.uptimeMillis(); break; case DELIVERY_DEFERRED: - if (deferUntilActive) deferredUntilActive[index] = true; + deferredCount++; + break; + case DELIVERY_DELIVERED: + case DELIVERY_SKIPPED: + case DELIVERY_TIMEOUT: + case DELIVERY_FAILURE: + terminalTime[index] = SystemClock.uptimeMillis(); + terminalCount++; break; } + + delivery[index] = newDeliveryState; + deliveryReasons[index] = reason; + + // If this state change might bring us to a new high-water mark, bring + // ourselves as high as we possibly can + final int oldBeyondCount = beyondCount; + if (index >= beyondCount) { + for (int i = beyondCount; i < delivery.length; i++) { + if (isDeliveryStateBeyond(getDeliveryState(i))) { + beyondCount = i + 1; + } else { + break; + } + } + } + return (beyondCount != oldBeyondCount); } @DeliveryState int getDeliveryState(int index) { return delivery[index]; } + /** + * @return if the given {@link #receivers} index should be considered + * blocked based on the current status of the overall broadcast. + */ + boolean isBlocked(int index) { + return (beyondCount < blockedUntilBeyondCount[index]); + } + boolean wasDeliveryAttempted(int index) { final int deliveryState = getDeliveryState(index); switch (deliveryState) { @@ -757,36 +813,36 @@ final class BroadcastRecord extends Binder { * has prioritized tranches of receivers. */ @VisibleForTesting - static boolean isPrioritized(@NonNull int[] blockedUntilTerminalCount, + static boolean isPrioritized(@NonNull int[] blockedUntilBeyondCount, boolean ordered) { - return !ordered && (blockedUntilTerminalCount.length > 0) - && (blockedUntilTerminalCount[0] != -1); + return !ordered && (blockedUntilBeyondCount.length > 0) + && (blockedUntilBeyondCount[0] != -1); } /** - * Calculate the {@link #terminalCount} that each receiver should be + * Calculate the {@link #beyondCount} that each receiver should be * considered blocked until. * <p> * For example, in an ordered broadcast, receiver {@code N} is blocked until - * receiver {@code N-1} reaches a terminal state. Similarly, in a - * prioritized broadcast, receiver {@code N} is blocked until all receivers - * of a higher priority reach a terminal state. + * receiver {@code N-1} reaches a terminal or deferred state. Similarly, in + * a prioritized broadcast, receiver {@code N} is blocked until all + * receivers of a higher priority reach a terminal or deferred state. * <p> - * When there are no terminal count constraints, the blocked value for each + * When there are no beyond count constraints, the blocked value for each * receiver is {@code -1}. */ @VisibleForTesting - static @NonNull int[] calculateBlockedUntilTerminalCount( + static @NonNull int[] calculateBlockedUntilBeyondCount( @NonNull List<Object> receivers, boolean ordered) { final int N = receivers.size(); - final int[] blockedUntilTerminalCount = new int[N]; + final int[] blockedUntilBeyondCount = new int[N]; int lastPriority = 0; int lastPriorityIndex = 0; for (int i = 0; i < N; i++) { if (ordered) { // When sending an ordered broadcast, we need to block this // receiver until all previous receivers have terminated - blockedUntilTerminalCount[i] = i; + blockedUntilBeyondCount[i] = i; } else { // When sending a prioritized broadcast, we only need to wait // for the previous tranche of receivers to be terminated @@ -794,18 +850,18 @@ final class BroadcastRecord extends Binder { if ((i == 0) || (thisPriority != lastPriority)) { lastPriority = thisPriority; lastPriorityIndex = i; - blockedUntilTerminalCount[i] = i; + blockedUntilBeyondCount[i] = i; } else { - blockedUntilTerminalCount[i] = lastPriorityIndex; + blockedUntilBeyondCount[i] = lastPriorityIndex; } } } // If the entire list is in the same priority tranche, mark as -1 to // indicate that none of them need to wait - if (N > 0 && blockedUntilTerminalCount[N - 1] == 0) { - Arrays.fill(blockedUntilTerminalCount, -1); + if (N > 0 && blockedUntilBeyondCount[N - 1] == 0) { + Arrays.fill(blockedUntilBeyondCount, -1); } - return blockedUntilTerminalCount; + return blockedUntilBeyondCount; } static int getReceiverUid(@NonNull Object receiver) { 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 8211d6fc03a2..ec177c9ac33d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java @@ -463,7 +463,8 @@ public final class BroadcastQueueModernImplTest { assertEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason()); // Bumping past barrier makes us now runnable - airplaneRecord.terminalCount++; + airplaneRecord.setDeliveryState(0, BroadcastRecord.DELIVERY_DELIVERED, + "testRunnableAt_Ordered"); queue.invalidateRunnableAt(); assertTrue(queue.isRunnable()); assertNotEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason()); 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 64a95ca843d3..d7ba3df07533 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -464,7 +464,7 @@ public class BroadcastQueueTest { doAnswer((invocation) -> { Log.v(TAG, "Intercepting scheduleReceiver() for " - + Arrays.toString(invocation.getArguments())); + + Arrays.toString(invocation.getArguments()) + " package " + ai.packageName); assertHealth(); final Intent intent = invocation.getArgument(0); final Bundle extras = invocation.getArgument(5); @@ -486,7 +486,7 @@ public class BroadcastQueueTest { doAnswer((invocation) -> { Log.v(TAG, "Intercepting scheduleRegisteredReceiver() for " - + Arrays.toString(invocation.getArguments())); + + Arrays.toString(invocation.getArguments()) + " package " + ai.packageName); assertHealth(); final Intent intent = invocation.getArgument(1); final Bundle extras = invocation.getArgument(4); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java index 2b6f2174d49b..08952eab071f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java @@ -24,7 +24,12 @@ import static android.content.Intent.ACTION_TIME_CHANGED; import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_ALL; import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY; import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE; -import static com.android.server.am.BroadcastRecord.calculateBlockedUntilTerminalCount; +import static com.android.server.am.BroadcastRecord.DELIVERY_DEFERRED; +import static com.android.server.am.BroadcastRecord.DELIVERY_DELIVERED; +import static com.android.server.am.BroadcastRecord.DELIVERY_PENDING; +import static com.android.server.am.BroadcastRecord.DELIVERY_SKIPPED; +import static com.android.server.am.BroadcastRecord.DELIVERY_TIMEOUT; +import static com.android.server.am.BroadcastRecord.calculateBlockedUntilBeyondCount; import static com.android.server.am.BroadcastRecord.calculateDeferUntilActive; import static com.android.server.am.BroadcastRecord.calculateUrgent; import static com.android.server.am.BroadcastRecord.isReceiverEquals; @@ -58,7 +63,6 @@ import androidx.test.filters.SmallTest; import com.android.server.am.BroadcastDispatcher.DeferredBootCompletedBroadcastPerUser; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -79,6 +83,7 @@ import java.util.function.BiFunction; @SmallTest @RunWith(MockitoJUnitRunner.class) public class BroadcastRecordTest { + private static final String TAG = "BroadcastRecordTest"; private static final int USER0 = UserHandle.USER_SYSTEM; private static final int USER1 = USER0 + 1; @@ -120,13 +125,13 @@ public class BroadcastRecordTest { assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), 10)))); assertArrayEquals(new int[] {-1}, - calculateBlockedUntilTerminalCount(List.of( + calculateBlockedUntilBeyondCount(List.of( createResolveInfo(PACKAGE1, getAppId(1), 0)), false)); assertArrayEquals(new int[] {-1}, - calculateBlockedUntilTerminalCount(List.of( + calculateBlockedUntilBeyondCount(List.of( createResolveInfo(PACKAGE1, getAppId(1), -10)), false)); assertArrayEquals(new int[] {-1}, - calculateBlockedUntilTerminalCount(List.of( + calculateBlockedUntilBeyondCount(List.of( createResolveInfo(PACKAGE1, getAppId(1), 10)), false)); } @@ -142,12 +147,12 @@ public class BroadcastRecordTest { createResolveInfo(PACKAGE3, getAppId(3), 10)))); assertArrayEquals(new int[] {-1,-1,-1}, - calculateBlockedUntilTerminalCount(List.of( + calculateBlockedUntilBeyondCount(List.of( createResolveInfo(PACKAGE1, getAppId(1), 0), createResolveInfo(PACKAGE2, getAppId(2), 0), createResolveInfo(PACKAGE3, getAppId(3), 0)), false)); assertArrayEquals(new int[] {-1,-1,-1}, - calculateBlockedUntilTerminalCount(List.of( + calculateBlockedUntilBeyondCount(List.of( createResolveInfo(PACKAGE1, getAppId(1), 10), createResolveInfo(PACKAGE2, getAppId(2), 10), createResolveInfo(PACKAGE3, getAppId(3), 10)), false)); @@ -156,26 +161,176 @@ public class BroadcastRecordTest { @Test public void testIsPrioritized_Yes() { assertTrue(isPrioritized(List.of( - createResolveInfo(PACKAGE1, getAppId(1), -10), + createResolveInfo(PACKAGE1, getAppId(1), 10), createResolveInfo(PACKAGE2, getAppId(2), 0), - createResolveInfo(PACKAGE3, getAppId(3), 10)))); + createResolveInfo(PACKAGE3, getAppId(3), -10)))); assertTrue(isPrioritized(List.of( - createResolveInfo(PACKAGE1, getAppId(1), 0), + createResolveInfo(PACKAGE1, getAppId(1), 10), createResolveInfo(PACKAGE2, getAppId(2), 0), - createResolveInfo(PACKAGE3, getAppId(3), 10)))); + createResolveInfo(PACKAGE3, getAppId(3), 0)))); assertArrayEquals(new int[] {0,1,2}, - calculateBlockedUntilTerminalCount(List.of( - createResolveInfo(PACKAGE1, getAppId(1), -10), + calculateBlockedUntilBeyondCount(List.of( + createResolveInfo(PACKAGE1, getAppId(1), 10), createResolveInfo(PACKAGE2, getAppId(2), 0), - createResolveInfo(PACKAGE3, getAppId(3), 10)), false)); + createResolveInfo(PACKAGE3, getAppId(3), -10)), false)); assertArrayEquals(new int[] {0,0,2,3,3}, - calculateBlockedUntilTerminalCount(List.of( - createResolveInfo(PACKAGE1, getAppId(1), 0), - createResolveInfo(PACKAGE2, getAppId(2), 0), + calculateBlockedUntilBeyondCount(List.of( + createResolveInfo(PACKAGE1, getAppId(1), 20), + createResolveInfo(PACKAGE2, getAppId(2), 20), createResolveInfo(PACKAGE3, getAppId(3), 10), - createResolveInfo(PACKAGE3, getAppId(3), 20), - createResolveInfo(PACKAGE3, getAppId(3), 20)), false)); + createResolveInfo(PACKAGE3, getAppId(3), 0), + createResolveInfo(PACKAGE3, getAppId(3), 0)), false)); + } + + @Test + public void testSetDeliveryState_Single() { + final BroadcastRecord r = createBroadcastRecord( + new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of( + createResolveInfoWithPriority(0))); + assertEquals(DELIVERY_PENDING, r.getDeliveryState(0)); + assertBlocked(r, false); + assertTerminalDeferredBeyond(r, 0, 0, 0); + + r.setDeliveryState(0, DELIVERY_DEFERRED, TAG); + assertEquals(DELIVERY_DEFERRED, r.getDeliveryState(0)); + assertBlocked(r, false); + assertTerminalDeferredBeyond(r, 0, 1, 1); + + // Identical state change has no effect + r.setDeliveryState(0, DELIVERY_DEFERRED, TAG); + assertEquals(DELIVERY_DEFERRED, r.getDeliveryState(0)); + assertBlocked(r, false); + assertTerminalDeferredBeyond(r, 0, 1, 1); + + // Moving to terminal state updates counters + r.setDeliveryState(0, DELIVERY_DELIVERED, TAG); + assertEquals(DELIVERY_DELIVERED, r.getDeliveryState(0)); + assertBlocked(r, false); + assertTerminalDeferredBeyond(r, 1, 0, 1); + + // Trying to change terminal state has no effect + r.setDeliveryState(0, DELIVERY_TIMEOUT, TAG); + assertEquals(DELIVERY_DELIVERED, r.getDeliveryState(0)); + assertBlocked(r, false); + assertTerminalDeferredBeyond(r, 1, 0, 1); + } + + @Test + public void testSetDeliveryState_Unordered() { + final BroadcastRecord r = createBroadcastRecord( + new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of( + createResolveInfoWithPriority(0), + createResolveInfoWithPriority(0), + createResolveInfoWithPriority(0))); + assertBlocked(r, false, false, false); + assertTerminalDeferredBeyond(r, 0, 0, 0); + + // Even though we finish a middle item in the tranche, we're not + // "beyond" it because there is still unfinished work before it + r.setDeliveryState(1, DELIVERY_DELIVERED, TAG); + assertBlocked(r, false, false, false); + assertTerminalDeferredBeyond(r, 1, 0, 0); + + r.setDeliveryState(0, DELIVERY_DELIVERED, TAG); + assertBlocked(r, false, false, false); + assertTerminalDeferredBeyond(r, 2, 0, 2); + + r.setDeliveryState(2, DELIVERY_DELIVERED, TAG); + assertBlocked(r, false, false, false); + assertTerminalDeferredBeyond(r, 3, 0, 3); + } + + @Test + public void testSetDeliveryState_Ordered() { + final BroadcastRecord r = createOrderedBroadcastRecord( + new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of( + createResolveInfoWithPriority(0), + createResolveInfoWithPriority(0), + createResolveInfoWithPriority(0))); + assertBlocked(r, false, true, true); + assertTerminalDeferredBeyond(r, 0, 0, 0); + + r.setDeliveryState(0, DELIVERY_DELIVERED, TAG); + assertBlocked(r, false, false, true); + assertTerminalDeferredBeyond(r, 1, 0, 1); + + r.setDeliveryState(1, DELIVERY_DELIVERED, TAG); + assertBlocked(r, false, false, false); + assertTerminalDeferredBeyond(r, 2, 0, 2); + + r.setDeliveryState(2, DELIVERY_DELIVERED, TAG); + assertBlocked(r, false, false, false); + assertTerminalDeferredBeyond(r, 3, 0, 3); + } + + @Test + public void testSetDeliveryState_DeferUntilActive() { + final BroadcastRecord r = createBroadcastRecord( + new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of( + createResolveInfoWithPriority(10), + createResolveInfoWithPriority(10), + createResolveInfoWithPriority(10), + createResolveInfoWithPriority(0), + createResolveInfoWithPriority(0), + createResolveInfoWithPriority(0), + createResolveInfoWithPriority(-10), + createResolveInfoWithPriority(-10), + createResolveInfoWithPriority(-10))); + assertBlocked(r, false, false, false, true, true, true, true, true, true); + assertTerminalDeferredBeyond(r, 0, 0, 0); + + r.setDeliveryState(0, DELIVERY_PENDING, TAG); + r.setDeliveryState(1, DELIVERY_DEFERRED, TAG); + r.setDeliveryState(2, DELIVERY_PENDING, TAG); + r.setDeliveryState(3, DELIVERY_DEFERRED, TAG); + r.setDeliveryState(4, DELIVERY_DEFERRED, TAG); + r.setDeliveryState(5, DELIVERY_DEFERRED, TAG); + r.setDeliveryState(6, DELIVERY_DEFERRED, TAG); + r.setDeliveryState(7, DELIVERY_PENDING, TAG); + r.setDeliveryState(8, DELIVERY_DEFERRED, TAG); + + // Verify deferred counts ratchet up, but we're not "beyond" the first + // still-pending receiver + assertBlocked(r, false, false, false, true, true, true, true, true, true); + assertTerminalDeferredBeyond(r, 0, 6, 0); + + // We're still not "beyond" the first still-pending receiver, even when + // we finish a receiver later in the first tranche + r.setDeliveryState(2, DELIVERY_DELIVERED, TAG); + assertBlocked(r, false, false, false, true, true, true, true, true, true); + assertTerminalDeferredBeyond(r, 1, 6, 0); + + // Completing that last item in first tranche means we now unblock the + // second tranche, and since it's entirely deferred, the third traunche + // is unblocked too + r.setDeliveryState(0, DELIVERY_DELIVERED, TAG); + assertBlocked(r, false, false, false, false, false, false, false, false, false); + assertTerminalDeferredBeyond(r, 2, 6, 7); + + // Moving a deferred item in an earlier tranche back to being pending + // doesn't change the fact that we've already moved beyond it + r.setDeliveryState(1, DELIVERY_PENDING, TAG); + assertBlocked(r, false, false, false, false, false, false, false, false, false); + assertTerminalDeferredBeyond(r, 2, 5, 7); + r.setDeliveryState(1, DELIVERY_DELIVERED, TAG); + assertBlocked(r, false, false, false, false, false, false, false, false, false); + assertTerminalDeferredBeyond(r, 3, 5, 7); + + // Completing middle pending item is enough to fast-forward to end + r.setDeliveryState(7, DELIVERY_DELIVERED, TAG); + assertBlocked(r, false, false, false, false, false, false, false, false, false); + assertTerminalDeferredBeyond(r, 4, 5, 9); + + // Moving everyone else directly into a finished state updates all the + // terminal counters + r.setDeliveryState(3, DELIVERY_SKIPPED, TAG); + r.setDeliveryState(4, DELIVERY_SKIPPED, TAG); + r.setDeliveryState(5, DELIVERY_SKIPPED, TAG); + r.setDeliveryState(6, DELIVERY_SKIPPED, TAG); + r.setDeliveryState(8, DELIVERY_SKIPPED, TAG); + assertBlocked(r, false, false, false, false, false, false, false, false, false); + assertTerminalDeferredBeyond(r, 9, 0, 9); } @Test @@ -688,6 +843,10 @@ public class BroadcastRecordTest { : errorMsg.insert(0, "Contains unexpected receiver: ").toString(); } + private static ResolveInfo createResolveInfoWithPriority(int priority) { + return createResolveInfo(PACKAGE1, getAppId(1), priority); + } + private static ResolveInfo createResolveInfo(String packageName, int uid) { return createResolveInfo(packageName, uid, 0); } @@ -738,21 +897,40 @@ public class BroadcastRecordTest { return excludedList; } + private BroadcastRecord createBroadcastRecord(Intent intent, + List<ResolveInfo> receivers) { + return createBroadcastRecord(receivers, USER0, intent, null /* filterExtrasForReceiver */, + null /* options */, false); + } + + private BroadcastRecord createOrderedBroadcastRecord(Intent intent, + List<ResolveInfo> receivers) { + return createBroadcastRecord(receivers, USER0, intent, null /* filterExtrasForReceiver */, + null /* options */, true); + } + private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId, Intent intent) { return createBroadcastRecord(receivers, userId, intent, null /* filterExtrasForReceiver */, - null /* options */); + null /* options */, false); } private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId, Intent intent, BroadcastOptions options) { return createBroadcastRecord(receivers, userId, intent, null /* filterExtrasForReceiver */, - options); + options, false); } private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId, Intent intent, BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver, BroadcastOptions options) { + return createBroadcastRecord(receivers, userId, intent, filterExtrasForReceiver, + options, false); + } + + private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId, + Intent intent, BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver, + BroadcastOptions options, boolean ordered) { return new BroadcastRecord( mQueue /* queue */, intent, @@ -774,7 +952,7 @@ public class BroadcastRecordTest { 0 /* resultCode */, null /* resultData */, null /* resultExtras */, - false /* serialized */, + ordered /* serialized */, false /* sticky */, false /* initialSticky */, userId, @@ -789,6 +967,20 @@ public class BroadcastRecordTest { private static boolean isPrioritized(List<Object> receivers) { return BroadcastRecord.isPrioritized( - calculateBlockedUntilTerminalCount(receivers, false), false); + calculateBlockedUntilBeyondCount(receivers, false), false); + } + + private static void assertBlocked(BroadcastRecord r, boolean... blocked) { + assertEquals(r.receivers.size(), blocked.length); + for (int i = 0; i < blocked.length; i++) { + assertEquals("blocked " + i, blocked[i], r.isBlocked(i)); + } + } + + private static void assertTerminalDeferredBeyond(BroadcastRecord r, + int expectedTerminalCount, int expectedDeferredCount, int expectedBeyondCount) { + assertEquals("terminal", expectedTerminalCount, r.terminalCount); + assertEquals("deferred", expectedDeferredCount, r.deferredCount); + assertEquals("beyond", expectedBeyondCount, r.beyondCount); } } |