Merge "Fix bug around deferred prioritized broadcasts." into udc-dev

This commit is contained in:
Jeff Sharkey
2023-04-11 00:33:20 +00:00
committed by Android (Google) Code Review
6 changed files with 342 additions and 119 deletions

View File

@@ -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());
}

View File

@@ -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");
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;
}
}
// Remember the old state and apply the new state
final int oldDeliveryState = getDeliveryState(r, index);
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);
}

View File

@@ -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) {
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:
deferredCount++;
break;
case DELIVERY_DELIVERED:
case DELIVERY_SKIPPED:
case DELIVERY_TIMEOUT:
case DELIVERY_FAILURE:
terminalTime[index] = SystemClock.uptimeMillis();
break;
case DELIVERY_SCHEDULED:
scheduledTime[index] = SystemClock.uptimeMillis();
break;
case DELIVERY_DEFERRED:
if (deferUntilActive) deferredUntilActive[index] = true;
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) {

View File

@@ -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());

View File

@@ -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);

View File

@@ -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);
}
}