Revert the batching code from the modern queue
Bug: 253906553 Revert the code that was added in support of batching. Test: atest * FrameworksMockingServicesTests:BroadcastQueueTest * FrameworksMockingServicesTests:BroadcastQueueModernImplTest Change-Id: If19ae85f795e0111cec5616df99f52529dba5814
This commit is contained in:
@@ -236,15 +236,6 @@ public class BroadcastConstants {
|
||||
private static final int DEFAULT_MAX_HISTORY_SUMMARY_SIZE =
|
||||
ActivityManager.isLowRamDeviceStatic() ? 25 : 300;
|
||||
|
||||
/**
|
||||
* For {@link BroadcastQueueModernImpl}: Maximum number of broadcast receivers to process in a
|
||||
* single synchronized block. Up to this many messages may be dispatched in a single binder
|
||||
* call. Set this to 1 (or zero) for pre-batch behavior.
|
||||
*/
|
||||
public int MAX_BROADCAST_BATCH_SIZE = DEFAULT_MAX_BROADCAST_BATCH_SIZE;
|
||||
private static final String KEY_MAX_BROADCAST_BATCH_SIZE = "bcast_max_batch_size";
|
||||
private static final int DEFAULT_MAX_BROADCAST_BATCH_SIZE = 1;
|
||||
|
||||
// Settings override tracking for this instance
|
||||
private String mSettingsKey;
|
||||
private SettingsObserver mSettingsObserver;
|
||||
@@ -382,8 +373,6 @@ public class BroadcastConstants {
|
||||
DEFAULT_MAX_HISTORY_COMPLETE_SIZE);
|
||||
MAX_HISTORY_SUMMARY_SIZE = getDeviceConfigInt(KEY_MAX_HISTORY_SUMMARY_SIZE,
|
||||
DEFAULT_MAX_HISTORY_SUMMARY_SIZE);
|
||||
MAX_BROADCAST_BATCH_SIZE = getDeviceConfigInt(KEY_MAX_BROADCAST_BATCH_SIZE,
|
||||
DEFAULT_MAX_BROADCAST_BATCH_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,7 +418,6 @@ public class BroadcastConstants {
|
||||
MAX_CONSECUTIVE_URGENT_DISPATCHES).println();
|
||||
pw.print(KEY_MAX_CONSECUTIVE_NORMAL_DISPATCHES,
|
||||
MAX_CONSECUTIVE_NORMAL_DISPATCHES).println();
|
||||
pw.print(KEY_MAX_BROADCAST_BATCH_SIZE, MAX_BROADCAST_BATCH_SIZE).println();
|
||||
pw.decreaseIndent();
|
||||
pw.println();
|
||||
}
|
||||
|
||||
@@ -73,7 +73,6 @@ import android.util.Slog;
|
||||
import android.util.SparseIntArray;
|
||||
import android.util.proto.ProtoOutputStream;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.os.TimeoutRecord;
|
||||
import com.android.internal.util.FrameworkStatsLog;
|
||||
import com.android.server.LocalServices;
|
||||
@@ -187,14 +186,6 @@ public class BroadcastQueueImpl extends BroadcastQueue {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This single object allows the queue to dispatch receivers using scheduleReceiverList
|
||||
* without constantly allocating new ReceiverInfo objects or ArrayLists. This queue
|
||||
* implementation is known to have a maximum size of one entry.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
final BroadcastReceiverBatch mReceiverBatch = new BroadcastReceiverBatch(1);
|
||||
|
||||
BroadcastQueueImpl(ActivityManagerService service, Handler handler,
|
||||
String name, BroadcastConstants constants, boolean allowDelayBehindServices,
|
||||
int schedGroup) {
|
||||
@@ -399,13 +390,13 @@ public class BroadcastQueueImpl extends BroadcastQueue {
|
||||
mService.notifyPackageUse(r.intent.getComponent().getPackageName(),
|
||||
PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
|
||||
final boolean assumeDelivered = false;
|
||||
thread.scheduleReceiverList(mReceiverBatch.manifestReceiver(
|
||||
thread.scheduleReceiver(
|
||||
prepareReceiverIntent(r.intent, r.curFilteredExtras),
|
||||
r.curReceiver, null /* compatInfo (unused but need to keep method signature) */,
|
||||
r.resultCode, r.resultData, r.resultExtras, r.ordered, assumeDelivered,
|
||||
r.userId, r.shareIdentity ? r.callingUid : Process.INVALID_UID,
|
||||
r.shareIdentity ? r.callerPackage : null,
|
||||
app.mState.getReportedProcState()));
|
||||
app.mState.getReportedProcState(),
|
||||
r.shareIdentity ? r.callerPackage : null);
|
||||
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
|
||||
"Process cur broadcast " + r + " DELIVERED for app " + app);
|
||||
started = true;
|
||||
@@ -746,12 +737,12 @@ public class BroadcastQueueImpl extends BroadcastQueue {
|
||||
// correctly ordered with other one-way calls.
|
||||
try {
|
||||
final boolean assumeDelivered = !ordered;
|
||||
thread.scheduleReceiverList(mReceiverBatch.registeredReceiver(
|
||||
thread.scheduleRegisteredReceiver(
|
||||
receiver, intent, resultCode,
|
||||
data, extras, ordered, sticky, assumeDelivered, sendingUser,
|
||||
app.mState.getReportedProcState(),
|
||||
shareIdentity ? callingUid : Process.INVALID_UID,
|
||||
shareIdentity ? callingPackage : null,
|
||||
app.mState.getReportedProcState()));
|
||||
shareIdentity ? callingPackage : null);
|
||||
} catch (RemoteException ex) {
|
||||
// Failed to call into the process. It's either dying or wedged. Kill it gently.
|
||||
synchronized (mService) {
|
||||
|
||||
@@ -143,11 +143,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
// We configure runnable size only once at boot; it'd be too complex to
|
||||
// try resizing dynamically at runtime
|
||||
mRunning = new BroadcastProcessQueue[mConstants.getMaxRunningQueues()];
|
||||
|
||||
// Set up the statistics for batched broadcasts.
|
||||
final int batchSize = mConstants.MAX_BROADCAST_BATCH_SIZE;
|
||||
mReceiverBatch = new BroadcastReceiverBatch(batchSize);
|
||||
Slog.i(TAG, "maximum broadcast batch size " + batchSize);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -217,15 +212,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
private final BroadcastConstants mFgConstants;
|
||||
private final BroadcastConstants mBgConstants;
|
||||
|
||||
/**
|
||||
* The sole instance of BroadcastReceiverBatch that is used by scheduleReceiverWarmLocked().
|
||||
* The class is not a true singleton but only one instance is needed for the broadcast queue.
|
||||
* Although this is guarded by mService, it should never be accessed by any other function.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
@GuardedBy("mService")
|
||||
final BroadcastReceiverBatch mReceiverBatch;
|
||||
|
||||
/**
|
||||
* Timestamp when last {@link #testAllProcessQueues} failure was observed;
|
||||
* used for throttling log messages.
|
||||
@@ -246,19 +232,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
|
||||
private void enqueueFinishReceiver(@NonNull BroadcastProcessQueue queue,
|
||||
@DeliveryState int deliveryState, @NonNull String reason) {
|
||||
enqueueFinishReceiver(queue, queue.getActive(), queue.getActiveIndex(),
|
||||
deliveryState, reason);
|
||||
}
|
||||
|
||||
private void enqueueFinishReceiver(@NonNull BroadcastProcessQueue queue,
|
||||
@NonNull BroadcastRecord r, int index,
|
||||
@DeliveryState int deliveryState, @NonNull String reason) {
|
||||
final SomeArgs args = SomeArgs.obtain();
|
||||
args.arg1 = queue;
|
||||
args.argi1 = deliveryState;
|
||||
args.arg2 = reason;
|
||||
args.arg3 = r;
|
||||
args.argi2 = index;
|
||||
mLocalHandler.sendMessage(Message.obtain(mLocalHandler, MSG_FINISH_RECEIVER, args));
|
||||
}
|
||||
|
||||
@@ -306,10 +283,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
final BroadcastProcessQueue queue = (BroadcastProcessQueue) args.arg1;
|
||||
final int deliveryState = args.argi1;
|
||||
final String reason = (String) args.arg2;
|
||||
final BroadcastRecord r = (BroadcastRecord) args.arg3;
|
||||
final int index = args.argi2;
|
||||
args.recycle();
|
||||
finishReceiverLocked(queue, deliveryState, reason, r, index);
|
||||
finishReceiverActiveLocked(queue, deliveryState, reason);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -765,7 +740,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
return;
|
||||
}
|
||||
|
||||
if (maybeSkipReceiver(queue, null, r, index)) {
|
||||
if (maybeSkipReceiver(queue, r, index)) {
|
||||
mRunningColdStart = null;
|
||||
return;
|
||||
}
|
||||
@@ -806,22 +781,20 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
@GuardedBy("mService")
|
||||
private void scheduleReceiverWarmLocked(@NonNull BroadcastProcessQueue queue) {
|
||||
checkState(queue.isActive(), "isActive");
|
||||
BroadcastReceiverBatch batch = mReceiverBatch;
|
||||
batch.reset();
|
||||
|
||||
while (collectReceiverList(queue, batch)) {
|
||||
if (batch.isFull()) {
|
||||
break;
|
||||
}
|
||||
if (!shouldContinueScheduling(queue)) {
|
||||
break;
|
||||
}
|
||||
if (queue.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
queue.makeActiveNextPending();
|
||||
final BroadcastRecord r = queue.getActive();
|
||||
final int index = queue.getActiveIndex();
|
||||
|
||||
if (r.terminalCount == 0) {
|
||||
r.dispatchTime = SystemClock.uptimeMillis();
|
||||
r.dispatchRealTime = SystemClock.elapsedRealtime();
|
||||
r.dispatchClockTime = System.currentTimeMillis();
|
||||
}
|
||||
processReceiverList(queue, batch);
|
||||
|
||||
if (maybeSkipReceiver(queue, r, index)) {
|
||||
return;
|
||||
}
|
||||
dispatchReceivers(queue, r, index);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -829,14 +802,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
* skipped (and therefore no more work is required).
|
||||
*/
|
||||
private boolean maybeSkipReceiver(@NonNull BroadcastProcessQueue queue,
|
||||
@Nullable BroadcastReceiverBatch batch, @NonNull BroadcastRecord r, int index) {
|
||||
@NonNull BroadcastRecord r, int index) {
|
||||
final String reason = shouldSkipReceiver(queue, r, index);
|
||||
if (reason != null) {
|
||||
if (batch == null) {
|
||||
enqueueFinishReceiver(queue, r, index, BroadcastRecord.DELIVERY_SKIPPED, reason);
|
||||
} else {
|
||||
batch.finish(r, index, BroadcastRecord.DELIVERY_SKIPPED, reason);
|
||||
}
|
||||
enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, reason);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -878,147 +847,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect receivers into a list, to be dispatched in a single receiver list call. Return
|
||||
* true if remaining receivers in the queue should be examined, and false if the current list
|
||||
* is complete.
|
||||
*/
|
||||
private boolean collectReceiverList(@NonNull BroadcastProcessQueue queue,
|
||||
@NonNull BroadcastReceiverBatch batch) {
|
||||
final ProcessRecord app = queue.app;
|
||||
final BroadcastRecord r = queue.getActive();
|
||||
final int index = queue.getActiveIndex();
|
||||
final Object receiver = r.receivers.get(index);
|
||||
final Intent receiverIntent = r.getReceiverIntent(receiver);
|
||||
|
||||
if (r.terminalCount == 0) {
|
||||
r.dispatchTime = SystemClock.uptimeMillis();
|
||||
r.dispatchRealTime = SystemClock.elapsedRealtime();
|
||||
r.dispatchClockTime = System.currentTimeMillis();
|
||||
}
|
||||
if (maybeSkipReceiver(queue, batch, r, index)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final IApplicationThread thread = app.getOnewayThread();
|
||||
if (thread == null) {
|
||||
batch.finish(r, index, BroadcastRecord.DELIVERY_FAILURE, "missing IApplicationThread");
|
||||
return true;
|
||||
}
|
||||
|
||||
final boolean assumeDelivered = isAssumedDelivered(r, index);
|
||||
if (receiver instanceof BroadcastFilter) {
|
||||
batch.schedule(((BroadcastFilter) receiver).receiverList.receiver,
|
||||
receiverIntent, r.resultCode, r.resultData, r.resultExtras,
|
||||
r.ordered, r.initialSticky, assumeDelivered, r.userId,
|
||||
r.shareIdentity ? r.callingUid : Process.INVALID_UID,
|
||||
r.shareIdentity ? r.callerPackage : null,
|
||||
app.mState.getReportedProcState(), r, index);
|
||||
// TODO: consider making registered receivers of unordered
|
||||
// broadcasts report results to detect ANRs
|
||||
if (assumeDelivered) {
|
||||
batch.success(r, index, BroadcastRecord.DELIVERY_DELIVERED, "assuming delivered");
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
batch.schedule(receiverIntent, ((ResolveInfo) receiver).activityInfo,
|
||||
null, r.resultCode, r.resultData, r.resultExtras, r.ordered, assumeDelivered,
|
||||
r.userId, r.shareIdentity ? r.callingUid : Process.INVALID_UID,
|
||||
r.shareIdentity ? r.callerPackage : null,
|
||||
app.mState.getReportedProcState(), r, index);
|
||||
if (assumeDelivered) {
|
||||
batch.success(r, index, BroadcastRecord.DELIVERY_DELIVERED, "assuming delivered");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the information in a BroadcastReceiverBatch. Elements in the finish and success
|
||||
* lists are sent to enqueueFinishReceiver(). Elements in the receivers list are transmitted
|
||||
* to the target in a single binder call.
|
||||
*/
|
||||
private void processReceiverList(@NonNull BroadcastProcessQueue queue,
|
||||
@NonNull BroadcastReceiverBatch batch) {
|
||||
// Transmit the receiver list.
|
||||
final ProcessRecord app = queue.app;
|
||||
final IApplicationThread thread = app.getOnewayThread();
|
||||
|
||||
batch.recordBatch(thread instanceof SameProcessApplicationThread);
|
||||
|
||||
// Mark all the receivers that were discarded. None of these have actually been scheduled.
|
||||
for (int i = 0; i < batch.finished().size(); i++) {
|
||||
final var finish = batch.finished().get(i);
|
||||
enqueueFinishReceiver(queue, finish.r, finish.index, finish.deliveryState,
|
||||
finish.reason);
|
||||
}
|
||||
// Prepare for delivery of all receivers that are about to be scheduled.
|
||||
for (int i = 0; i < batch.cookies().size(); i++) {
|
||||
final var cookie = batch.cookies().get(i);
|
||||
prepareToDispatch(queue, cookie.r, cookie.index);
|
||||
}
|
||||
|
||||
// Notify on dispatch. Note that receiver/cookies are recorded only if the thread is
|
||||
// non-null and the list will therefore be sent.
|
||||
for (int i = 0; i < batch.cookies().size(); i++) {
|
||||
// Cookies and receivers are 1:1
|
||||
final var cookie = batch.cookies().get(i);
|
||||
final BroadcastRecord r = cookie.r;
|
||||
final int index = cookie.index;
|
||||
final Object receiver = r.receivers.get(index);
|
||||
|
||||
if (r.shareIdentity) {
|
||||
mService.mPackageManagerInt.grantImplicitAccess(r.userId, r.intent,
|
||||
UserHandle.getAppId(app.uid), r.callingUid, true);
|
||||
}
|
||||
if (receiver instanceof BroadcastFilter) {
|
||||
notifyScheduleRegisteredReceiver(queue.app, r, (BroadcastFilter) receiver);
|
||||
} else {
|
||||
notifyScheduleReceiver(queue.app, r, (ResolveInfo) receiver);
|
||||
}
|
||||
}
|
||||
|
||||
// Transmit the enqueued receivers. The thread cannot be null because the lock has been
|
||||
// held since collectReceiverList(), which will not add any receivers if the thread is null.
|
||||
boolean remoteFailed = false;
|
||||
if (batch.receivers().size() > 0) {
|
||||
try {
|
||||
thread.scheduleReceiverList(batch.receivers());
|
||||
} catch (RemoteException e) {
|
||||
// Log the failure of the first receiver in the list. Note that there must be at
|
||||
// least one receiver/cookie to reach this point in the code, which means
|
||||
// cookie[0] is a valid element.
|
||||
final var info = batch.cookies().get(0);
|
||||
final BroadcastRecord r = info.r;
|
||||
final int index = info.index;
|
||||
final Object receiver = r.receivers.get(index);
|
||||
final String msg = "Failed to schedule " + r + " to " + receiver
|
||||
+ " via " + app + ": " + e;
|
||||
logw(msg);
|
||||
app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER, true);
|
||||
remoteFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!remoteFailed) {
|
||||
// If transmission succeed, report all receivers that are assumed to be delivered.
|
||||
for (int i = 0; i < batch.success().size(); i++) {
|
||||
final var finish = batch.success().get(i);
|
||||
enqueueFinishReceiver(queue, finish.r, finish.index, finish.deliveryState,
|
||||
finish.reason);
|
||||
}
|
||||
} else {
|
||||
// If transmission failed, fail all receivers in the list.
|
||||
for (int i = 0; i < batch.cookies().size(); i++) {
|
||||
final var cookie = batch.cookies().get(i);
|
||||
enqueueFinishReceiver(queue, cookie.r, cookie.index,
|
||||
BroadcastRecord.DELIVERY_FAILURE, "remote app");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this receiver should be assumed to have been delivered.
|
||||
*/
|
||||
@@ -1030,7 +858,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
/**
|
||||
* A receiver is about to be dispatched. Start ANR timers, if necessary.
|
||||
*/
|
||||
private void prepareToDispatch(@NonNull BroadcastProcessQueue queue,
|
||||
private void dispatchReceivers(@NonNull BroadcastProcessQueue queue,
|
||||
@NonNull BroadcastRecord r, int index) {
|
||||
final ProcessRecord app = queue.app;
|
||||
final Object receiver = r.receivers.get(index);
|
||||
@@ -1069,6 +897,47 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
if (DEBUG_BROADCAST) logv("Scheduling " + r + " to warm " + app);
|
||||
setDeliveryState(queue, app, r, index, receiver, BroadcastRecord.DELIVERY_SCHEDULED,
|
||||
"scheduleReceiverWarmLocked");
|
||||
|
||||
final Intent receiverIntent = r.getReceiverIntent(receiver);
|
||||
final IApplicationThread thread = app.getOnewayThread();
|
||||
if (thread != null) {
|
||||
try {
|
||||
if (receiver instanceof BroadcastFilter) {
|
||||
notifyScheduleRegisteredReceiver(app, r, (BroadcastFilter) receiver);
|
||||
thread.scheduleRegisteredReceiver(
|
||||
((BroadcastFilter) receiver).receiverList.receiver,
|
||||
receiverIntent, r.resultCode, r.resultData, r.resultExtras,
|
||||
r.ordered, r.initialSticky, assumeDelivered, r.userId,
|
||||
app.mState.getReportedProcState(),
|
||||
r.shareIdentity ? r.callingUid : Process.INVALID_UID,
|
||||
r.shareIdentity ? r.callerPackage : null);
|
||||
// TODO: consider making registered receivers of unordered
|
||||
// broadcasts report results to detect ANRs
|
||||
if (assumeDelivered) {
|
||||
enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_DELIVERED,
|
||||
"assuming delivered");
|
||||
}
|
||||
} else {
|
||||
notifyScheduleReceiver(app, r, (ResolveInfo) receiver);
|
||||
thread.scheduleReceiver(receiverIntent, ((ResolveInfo) receiver).activityInfo,
|
||||
null, r.resultCode, r.resultData, r.resultExtras, r.ordered,
|
||||
assumeDelivered, r.userId,
|
||||
app.mState.getReportedProcState(),
|
||||
r.shareIdentity ? r.callingUid : Process.INVALID_UID,
|
||||
r.shareIdentity ? r.callerPackage : null);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
final String msg = "Failed to schedule " + r + " to " + receiver
|
||||
+ " via " + app + ": " + e;
|
||||
logw(msg);
|
||||
app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER,
|
||||
ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true);
|
||||
enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_FAILURE, "remote app");
|
||||
}
|
||||
} else {
|
||||
enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_FAILURE,
|
||||
"missing IApplicationThread");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1088,13 +957,13 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
}
|
||||
try {
|
||||
final boolean assumeDelivered = true;
|
||||
thread.scheduleReceiverList(mReceiverBatch.registeredReceiver(
|
||||
thread.scheduleRegisteredReceiver(
|
||||
r.resultTo, r.intent,
|
||||
r.resultCode, r.resultData, r.resultExtras, false, r.initialSticky,
|
||||
assumeDelivered, r.userId,
|
||||
app.mState.getReportedProcState(),
|
||||
r.shareIdentity ? r.callingUid : Process.INVALID_UID,
|
||||
r.shareIdentity ? r.callerPackage : null,
|
||||
app.mState.getReportedProcState()));
|
||||
r.shareIdentity ? r.callerPackage : null);
|
||||
} catch (RemoteException e) {
|
||||
final String msg = "Failed to schedule result of " + r + " via " + app + ": " + e;
|
||||
logw(msg);
|
||||
@@ -1182,33 +1051,20 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
return false;
|
||||
}
|
||||
|
||||
final BroadcastRecord r = queue.getActive();
|
||||
final int index = queue.getActiveIndex();
|
||||
return finishReceiverLocked(queue, deliveryState, reason, r, index);
|
||||
}
|
||||
|
||||
private boolean finishReceiverLocked(@NonNull BroadcastProcessQueue queue,
|
||||
@DeliveryState int deliveryState, @NonNull String reason,
|
||||
BroadcastRecord r, int index) {
|
||||
if (!queue.isActive()) {
|
||||
logw("Ignoring finish; no active broadcast for " + queue);
|
||||
return false;
|
||||
}
|
||||
|
||||
final int cookie = traceBegin("finishReceiver");
|
||||
final ProcessRecord app = queue.app;
|
||||
final BroadcastRecord r = queue.getActive();
|
||||
final int index = queue.getActiveIndex();
|
||||
final Object receiver = r.receivers.get(index);
|
||||
|
||||
setDeliveryState(queue, app, r, index, receiver, deliveryState, reason);
|
||||
|
||||
final boolean early = r != queue.getActive() || index != queue.getActiveIndex();
|
||||
|
||||
if (deliveryState == BroadcastRecord.DELIVERY_TIMEOUT) {
|
||||
r.anrCount++;
|
||||
if (app != null && !app.isDebugging()) {
|
||||
mService.appNotResponding(queue.app, TimeoutRecord.forBroadcastReceiver(r.intent));
|
||||
}
|
||||
} else if (!early) {
|
||||
} else {
|
||||
mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_SOFT, queue);
|
||||
mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_HARD, queue);
|
||||
}
|
||||
@@ -1216,13 +1072,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
// Given that a receiver just finished, check if the "waitingFor" conditions are met.
|
||||
checkAndRemoveWaitingFor();
|
||||
|
||||
if (early) {
|
||||
// This is an early receiver that was transmitted as part of a group. The delivery
|
||||
// state has been updated but don't make any further decisions.
|
||||
traceEnd(cookie);
|
||||
return false;
|
||||
}
|
||||
|
||||
final boolean res = shouldContinueScheduling(queue);
|
||||
if (res) {
|
||||
// We're on a roll; move onto the next broadcast for this process
|
||||
@@ -1970,17 +1819,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
ipw.decreaseIndent();
|
||||
ipw.println();
|
||||
|
||||
ipw.println("Batch statistics:");
|
||||
ipw.increaseIndent();
|
||||
{
|
||||
final var stats = mReceiverBatch.getStatistics();
|
||||
ipw.println("Finished " + Arrays.toString(stats.finish));
|
||||
ipw.println("DispatchedLocal " + Arrays.toString(stats.local));
|
||||
ipw.println("DispatchedRemote " + Arrays.toString(stats.remote));
|
||||
}
|
||||
ipw.decreaseIndent();
|
||||
ipw.println();
|
||||
|
||||
if (dumpConstants) {
|
||||
mConstants.dump(ipw);
|
||||
}
|
||||
|
||||
@@ -1,357 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.am;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.ReceiverInfo;
|
||||
import android.content.IIntentReceiver;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.res.CompatibilityInfo;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* A batch of receiver instructions. This includes a list of finish requests and a list of
|
||||
* receivers. The instructions are for a single queue. It is constructed and consumed in a single
|
||||
* call to {@link BroadcastQueueModernImpl#scheduleReceiverWarmLocked}. The list size is bounded by
|
||||
* {@link BroadcastConstants#MAX_BROADCAST_BATCH_SIZE}. Because this class is ephemeral and its use
|
||||
* is bounded, it is pre-allocated to avoid allocating new objects every time it is used.
|
||||
*
|
||||
* The {@link #single} methods support the use of this class in {@link BroadcastQueueImpl}. These
|
||||
* methods simplify the use of {@link IApplicationThread#scheduleReceiverList} as a replacement
|
||||
* for scheduleReceiver and scheduleRegisteredReceiver.
|
||||
*
|
||||
* This class is designed to be allocated once and constantly reused (call {@link #reset} between
|
||||
* uses). Objects needed by the instance are kept in a pool - all object allocation occurs when
|
||||
* the instance is created, and no allocation occurs thereafter. However, if the variable
|
||||
* mDeepReceiverCopy is set true (it is false by default) then the method {@link #receivers} always
|
||||
* returns newly allocated objects. This is required for mock testing.
|
||||
*
|
||||
* This class is not thread-safe. Instances must be protected by the caller.
|
||||
* @hide
|
||||
*/
|
||||
final class BroadcastReceiverBatch {
|
||||
|
||||
/**
|
||||
* If this is true then receivers() returns a deep copy of the ReceiveInfo array. If this is
|
||||
* false, receivers() returns a reference to the array. A deep copy is needed only for the
|
||||
* broadcast queue mocking tests.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
boolean mDeepReceiverCopy = false;
|
||||
|
||||
/**
|
||||
* A private pool implementation this class.
|
||||
*/
|
||||
private static class Pool<T> {
|
||||
final int size;
|
||||
final ArrayList<T> pool;
|
||||
int next;
|
||||
Pool(int n, @NonNull Class<T> c) {
|
||||
size = n;
|
||||
pool = new ArrayList<>(size);
|
||||
try {
|
||||
for (int i = 0; i < size; i++) {
|
||||
pool.add(c.getDeclaredConstructor().newInstance());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// This class is only used locally. Turn any exceptions into something fatal.
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
T next() {
|
||||
return pool.get(next++);
|
||||
}
|
||||
void reset() {
|
||||
next = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The information needed to finish off a receiver. This is valid only in the context of
|
||||
* a queue.
|
||||
*/
|
||||
static class FinishInfo {
|
||||
BroadcastRecord r = null;
|
||||
int index = 0;
|
||||
int deliveryState = 0;
|
||||
String reason = null;
|
||||
FinishInfo set(@Nullable BroadcastRecord r, int index,
|
||||
int deliveryState, @Nullable String reason) {
|
||||
this.r = r;
|
||||
this.index = index;
|
||||
this.deliveryState = deliveryState;
|
||||
this.reason = reason;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The information needed to recreate a receiver info. The broadcast record can be null if the
|
||||
* caller does not expect to need it later.
|
||||
*/
|
||||
static class ReceiverCookie {
|
||||
BroadcastRecord r = null;
|
||||
int index = 0;
|
||||
ReceiverCookie set(@Nullable BroadcastRecord r, int index) {
|
||||
this.r = r;
|
||||
this.index = index;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
// The object pools.
|
||||
final int mSize;
|
||||
private final Pool<ReceiverInfo> receiverPool;
|
||||
private final Pool<FinishInfo> finishPool;
|
||||
private final Pool<ReceiverCookie> cookiePool;
|
||||
|
||||
// The accumulated data. The receivers should be an ArrayList to be directly compatible
|
||||
// with scheduleReceiverList(). The receivers array is not final because a new array must
|
||||
// be created for every new call to scheduleReceiverList().
|
||||
private final ArrayList<ReceiverInfo> mReceivers;
|
||||
private final ArrayList<ReceiverCookie> mCookies;
|
||||
private final ArrayList<FinishInfo> mFinished;
|
||||
// The list of finish records to complete if the binder succeeds or fails.
|
||||
private final ArrayList<FinishInfo> mSuccess;
|
||||
|
||||
BroadcastReceiverBatch(int size) {
|
||||
mSize = size;
|
||||
mReceivers = new ArrayList<>(mSize);
|
||||
mCookies = new ArrayList<>(mSize);
|
||||
mFinished = new ArrayList<>(mSize);
|
||||
mSuccess = new ArrayList<>(mSize);
|
||||
|
||||
receiverPool = new Pool<>(mSize, ReceiverInfo.class);
|
||||
finishPool = new Pool<>(mSize, FinishInfo.class);
|
||||
cookiePool = new Pool<>(mSize, ReceiverCookie.class);
|
||||
mStats = new Statistics(mSize);
|
||||
reset();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
mReceivers.clear();
|
||||
mCookies.clear();
|
||||
mFinished.clear();
|
||||
mSuccess.clear();
|
||||
|
||||
receiverPool.reset();
|
||||
finishPool.reset();
|
||||
cookiePool.reset();
|
||||
}
|
||||
|
||||
void finish(@Nullable BroadcastRecord r, int index,
|
||||
int deliveryState, @Nullable String reason) {
|
||||
mFinished.add(finishPool.next().set(r, index, deliveryState, reason));
|
||||
}
|
||||
void success(@Nullable BroadcastRecord r, int index,
|
||||
int deliveryState, @Nullable String reason) {
|
||||
mSuccess.add(finishPool.next().set(r, index, deliveryState, reason));
|
||||
}
|
||||
// Add a ReceiverInfo for a registered receiver.
|
||||
void schedule(@Nullable IIntentReceiver receiver, Intent intent,
|
||||
int resultCode, @Nullable String data, @Nullable Bundle extras, boolean ordered,
|
||||
boolean sticky, boolean assumeDelivered, int sendingUser, int sendingUid,
|
||||
@Nullable String sendingPackage, int processState, @Nullable BroadcastRecord r,
|
||||
int index) {
|
||||
ReceiverInfo ri = new ReceiverInfo();
|
||||
ri.intent = intent;
|
||||
ri.data = data;
|
||||
ri.extras = extras;
|
||||
ri.assumeDelivered = assumeDelivered;
|
||||
ri.sendingUser = sendingUser;
|
||||
ri.processState = processState;
|
||||
ri.resultCode = resultCode;
|
||||
ri.registered = true;
|
||||
ri.receiver = receiver;
|
||||
ri.ordered = ordered;
|
||||
ri.sticky = sticky;
|
||||
ri.sendingUid = sendingUid;
|
||||
ri.sendingPackage = sendingPackage;
|
||||
|
||||
mReceivers.add(ri);
|
||||
mCookies.add(cookiePool.next().set(r, index));
|
||||
}
|
||||
// Add a ReceiverInfo for a manifest receiver.
|
||||
void schedule(@Nullable Intent intent, @Nullable ActivityInfo activityInfo,
|
||||
@Nullable CompatibilityInfo compatInfo, int resultCode, @Nullable String data,
|
||||
@Nullable Bundle extras, boolean sync, boolean assumeDelivered, int sendingUser,
|
||||
int sendingUid, @Nullable String sendingPackage, int processState,
|
||||
@Nullable BroadcastRecord r, int index) {
|
||||
ReceiverInfo ri = new ReceiverInfo();
|
||||
ri.intent = intent;
|
||||
ri.data = data;
|
||||
ri.extras = extras;
|
||||
ri.assumeDelivered = assumeDelivered;
|
||||
ri.sendingUser = sendingUser;
|
||||
ri.processState = processState;
|
||||
ri.resultCode = resultCode;
|
||||
ri.registered = false;
|
||||
ri.activityInfo = activityInfo;
|
||||
ri.compatInfo = compatInfo;
|
||||
ri.sync = sync;
|
||||
ri.sendingUid = sendingUid;
|
||||
ri.sendingPackage = sendingPackage;
|
||||
mReceivers.add(ri);
|
||||
mCookies.add(cookiePool.next().set(r, index));
|
||||
}
|
||||
|
||||
/**
|
||||
* Two convenience functions for dispatching a single receiver. The functions start with a
|
||||
* reset. Then they create the ReceiverInfo array and return it. Statistics are not
|
||||
* collected.
|
||||
*/
|
||||
ArrayList<ReceiverInfo> registeredReceiver(@Nullable IIntentReceiver receiver,
|
||||
@Nullable Intent intent, int resultCode, @Nullable String data,
|
||||
@Nullable Bundle extras, boolean ordered, boolean sticky, boolean assumeDelivered,
|
||||
int sendingUser, int sendingUid, @Nullable String sendingPackage, int processState) {
|
||||
reset();
|
||||
schedule(receiver, intent, resultCode, data, extras, ordered, sticky, assumeDelivered,
|
||||
sendingUser, sendingUid, sendingPackage, processState, null, 0);
|
||||
return receivers();
|
||||
}
|
||||
|
||||
ArrayList<ReceiverInfo> manifestReceiver(@Nullable Intent intent,
|
||||
@Nullable ActivityInfo activityInfo, @Nullable CompatibilityInfo compatInfo,
|
||||
int resultCode, @Nullable String data, @Nullable Bundle extras, boolean sync,
|
||||
boolean assumeDelivered, int sendingUser, int sendingUid,
|
||||
@Nullable String sendingPackage, int processState) {
|
||||
reset();
|
||||
schedule(intent, activityInfo, compatInfo, resultCode, data, extras, sync, assumeDelivered,
|
||||
sendingUser, sendingUid, sendingPackage, processState, null, 0);
|
||||
return receivers();
|
||||
}
|
||||
|
||||
// Return true if the batch is full. Adding any more entries will throw an exception.
|
||||
boolean isFull() {
|
||||
return (mFinished.size() + mReceivers.size()) >= mSize;
|
||||
}
|
||||
int finishCount() {
|
||||
return mFinished.size() + mSuccess.size();
|
||||
}
|
||||
int receiverCount() {
|
||||
return mReceivers.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a deep copy of the receiver list. This is only for testing which is confused when
|
||||
* objects are reused.
|
||||
*/
|
||||
private ArrayList<ReceiverInfo> copyReceiverInfo() {
|
||||
ArrayList<ReceiverInfo> copy = new ArrayList<>();
|
||||
for (int i = 0; i < mReceivers.size(); i++) {
|
||||
final ReceiverInfo r = mReceivers.get(i);
|
||||
final ReceiverInfo n = new ReceiverInfo();
|
||||
n.intent = r.intent;
|
||||
n.data = r.data;
|
||||
n.extras = r.extras;
|
||||
n.assumeDelivered = r.assumeDelivered;
|
||||
n.sendingUser = r.sendingUser;
|
||||
n.processState = r.processState;
|
||||
n.resultCode = r.resultCode;
|
||||
n.registered = r.registered;
|
||||
n.receiver = r.receiver;
|
||||
n.ordered = r.ordered;
|
||||
n.sticky = r.sticky;
|
||||
n.activityInfo = r.activityInfo;
|
||||
n.compatInfo = r.compatInfo;
|
||||
n.sync = r.sync;
|
||||
copy.add(n);
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessors for the accumulated instructions. The important accessor is receivers(), since
|
||||
* it can be modified to return a deep copy of the mReceivers array.
|
||||
*/
|
||||
@NonNull
|
||||
ArrayList<ReceiverInfo> receivers() {
|
||||
if (!mDeepReceiverCopy) {
|
||||
return mReceivers;
|
||||
} else {
|
||||
return copyReceiverInfo();
|
||||
}
|
||||
}
|
||||
@NonNull
|
||||
ArrayList<ReceiverCookie> cookies() {
|
||||
return mCookies;
|
||||
}
|
||||
@NonNull
|
||||
ArrayList<FinishInfo> finished() {
|
||||
return mFinished;
|
||||
}
|
||||
@NonNull
|
||||
ArrayList<FinishInfo> success() {
|
||||
return mSuccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple POD for statistics. The parameter is the size of the BroadcastReceiverBatch.
|
||||
*/
|
||||
static class Statistics {
|
||||
final int[] finish;
|
||||
final int[] local;
|
||||
final int[] remote;
|
||||
Statistics(int size) {
|
||||
finish = new int[size+1];
|
||||
local = new int[size+1];
|
||||
remote = new int[size+1];
|
||||
}
|
||||
}
|
||||
|
||||
private final Statistics mStats;
|
||||
|
||||
/**
|
||||
* A unique counter that identifies individual transmission groups. This is only used for
|
||||
* debugging. It is used to determine which receivers were sent in the same batch, and in
|
||||
* which order. This is static to distinguish between batches across all queues in the
|
||||
* system.
|
||||
*/
|
||||
private static final AtomicInteger sTransmitGroup = new AtomicInteger(0);
|
||||
|
||||
/**
|
||||
* Record statistics for this batch of instructions. This updates the local statistics and it
|
||||
* updates the transmitGroup and transmitOrder fields of the BroadcastRecords being
|
||||
* dispatched.
|
||||
*/
|
||||
void recordBatch(boolean local) {
|
||||
final int group = sTransmitGroup.addAndGet(1);
|
||||
for (int i = 0; i < cookies().size(); i++) {
|
||||
final var cookie = cookies().get(i);
|
||||
cookie.r.transmitGroup[cookie.index] = group;
|
||||
cookie.r.transmitOrder[cookie.index] = i;
|
||||
}
|
||||
mStats.finish[finishCount()]++;
|
||||
if (local) {
|
||||
mStats.local[receiverCount()]++;
|
||||
} else {
|
||||
mStats.remote[receiverCount()]++;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
Statistics getStatistics() {
|
||||
return mStats;
|
||||
}
|
||||
}
|
||||
@@ -120,8 +120,6 @@ final class BroadcastRecord extends Binder {
|
||||
@UptimeMillisLong long finishTime; // when broadcast finished
|
||||
final @UptimeMillisLong long[] scheduledTime; // when each receiver was scheduled
|
||||
final @UptimeMillisLong long[] terminalTime; // when each receiver was terminal
|
||||
final int[] transmitGroup; // the batch group for each receiver
|
||||
final int[] transmitOrder; // the position of the receiver in the group
|
||||
final boolean timeoutExempt; // true if this broadcast is not subject to receiver timeouts
|
||||
int resultCode; // current result code value.
|
||||
@Nullable String resultData; // current result data value.
|
||||
@@ -398,8 +396,6 @@ final class BroadcastRecord extends Binder {
|
||||
blockedUntilTerminalCount = calculateBlockedUntilTerminalCount(receivers, _serialized);
|
||||
scheduledTime = new long[delivery.length];
|
||||
terminalTime = new long[delivery.length];
|
||||
transmitGroup = new int[delivery.length];
|
||||
transmitOrder = new int[delivery.length];
|
||||
resultToApp = _resultToApp;
|
||||
resultTo = _resultTo;
|
||||
resultCode = _resultCode;
|
||||
@@ -455,8 +451,6 @@ final class BroadcastRecord extends Binder {
|
||||
blockedUntilTerminalCount = from.blockedUntilTerminalCount;
|
||||
scheduledTime = from.scheduledTime;
|
||||
terminalTime = from.terminalTime;
|
||||
transmitGroup = from.transmitGroup;
|
||||
transmitOrder = from.transmitOrder;
|
||||
resultToApp = from.resultToApp;
|
||||
resultTo = from.resultTo;
|
||||
enqueueTime = from.enqueueTime;
|
||||
|
||||
@@ -53,7 +53,6 @@ import android.app.AppOpsManager;
|
||||
import android.app.BackgroundStartPrivileges;
|
||||
import android.app.BroadcastOptions;
|
||||
import android.app.IApplicationThread;
|
||||
import android.app.ReceiverInfo;
|
||||
import android.app.usage.UsageEvents.Event;
|
||||
import android.app.usage.UsageStatsManagerInternal;
|
||||
import android.content.ComponentName;
|
||||
@@ -66,7 +65,6 @@ import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.res.CompatibilityInfo;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.DeadObjectException;
|
||||
@@ -298,16 +296,12 @@ public class BroadcastQueueTest {
|
||||
};
|
||||
|
||||
if (mImpl == Impl.DEFAULT) {
|
||||
var q = new BroadcastQueueImpl(mAms, mHandlerThread.getThreadHandler(), TAG,
|
||||
mQueue = new BroadcastQueueImpl(mAms, mHandlerThread.getThreadHandler(), TAG,
|
||||
mConstants, mSkipPolicy, emptyHistory, false,
|
||||
ProcessList.SCHED_GROUP_DEFAULT);
|
||||
q.mReceiverBatch.mDeepReceiverCopy = true;
|
||||
mQueue = q;
|
||||
} else if (mImpl == Impl.MODERN) {
|
||||
var q = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(),
|
||||
mQueue = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(),
|
||||
mConstants, mConstants, mSkipPolicy, emptyHistory);
|
||||
q.mReceiverBatch.mDeepReceiverCopy = true;
|
||||
mQueue = q;
|
||||
} else {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
@@ -406,43 +400,6 @@ public class BroadcastQueueTest {
|
||||
UnaryOperator.identity());
|
||||
}
|
||||
|
||||
private void doRegisteredReceiver(ProcessRecord r, boolean wedge, boolean abort,
|
||||
UnaryOperator<Bundle> extrasOperator, ReceiverInfo info) {
|
||||
final Intent intent = info.intent;
|
||||
final Bundle extras = info.extras;
|
||||
final boolean assumeDelivered = info.assumeDelivered;
|
||||
mScheduledBroadcasts.add(makeScheduledBroadcast(r, intent));
|
||||
if (!wedge && !assumeDelivered) {
|
||||
assertTrue(r.mReceivers.numberOfCurReceivers() > 0);
|
||||
assertNotEquals(ProcessList.SCHED_GROUP_UNDEFINED,
|
||||
mQueue.getPreferredSchedulingGroupLocked(r));
|
||||
mHandlerThread.getThreadHandler().post(() -> {
|
||||
synchronized (mAms) {
|
||||
mQueue.finishReceiverLocked(r, Activity.RESULT_OK,
|
||||
null, extrasOperator.apply(extras), abort, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void doManifestReceiver(ProcessRecord r, boolean wedge, boolean abort,
|
||||
UnaryOperator<Bundle> extrasOperator, ReceiverInfo info) {
|
||||
final Intent intent = info.intent;
|
||||
final Bundle extras = info.extras;
|
||||
mScheduledBroadcasts.add(makeScheduledBroadcast(r, intent));
|
||||
if (!wedge) {
|
||||
assertTrue(r.mReceivers.numberOfCurReceivers() > 0);
|
||||
assertNotEquals(ProcessList.SCHED_GROUP_UNDEFINED,
|
||||
mQueue.getPreferredSchedulingGroupLocked(r));
|
||||
mHandlerThread.getThreadHandler().post(() -> {
|
||||
synchronized (mAms) {
|
||||
mQueue.finishReceiverLocked(r, Activity.RESULT_OK, null,
|
||||
extrasOperator.apply(extras), abort, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private ProcessRecord makeActiveProcessRecord(ApplicationInfo ai, String processName,
|
||||
ProcessBehavior behavior, UnaryOperator<Bundle> extrasOperator) throws Exception {
|
||||
final boolean wedge = (behavior == ProcessBehavior.WEDGE);
|
||||
@@ -486,22 +443,47 @@ public class BroadcastQueueTest {
|
||||
if (dead) return r;
|
||||
|
||||
doAnswer((invocation) -> {
|
||||
Log.v(TAG, "Intercepting scheduleReceiverList() for "
|
||||
Log.v(TAG, "Intercepting scheduleReceiver() for "
|
||||
+ Arrays.toString(invocation.getArguments()));
|
||||
final List<ReceiverInfo> data = invocation.getArgument(0);
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
ReceiverInfo info = data.get(i);
|
||||
// The logic here mimics the logic in ActivityThread: elements of the list are
|
||||
// forwarded to a handler for manifest receivers or to a handler for registered
|
||||
// receivers.
|
||||
if (info.registered) {
|
||||
doRegisteredReceiver(r, wedge, abort, extrasOperator, info);
|
||||
} else {
|
||||
doManifestReceiver(r, wedge, abort, extrasOperator, info);
|
||||
}
|
||||
final Intent intent = invocation.getArgument(0);
|
||||
final Bundle extras = invocation.getArgument(5);
|
||||
mScheduledBroadcasts.add(makeScheduledBroadcast(r, intent));
|
||||
if (!wedge) {
|
||||
assertTrue(r.mReceivers.numberOfCurReceivers() > 0);
|
||||
assertNotEquals(ProcessList.SCHED_GROUP_UNDEFINED,
|
||||
mQueue.getPreferredSchedulingGroupLocked(r));
|
||||
mHandlerThread.getThreadHandler().post(() -> {
|
||||
synchronized (mAms) {
|
||||
mQueue.finishReceiverLocked(r, Activity.RESULT_OK, null,
|
||||
extrasOperator.apply(extras), abort, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}).when(thread).scheduleReceiverList(any());
|
||||
}).when(thread).scheduleReceiver(any(), any(), any(), anyInt(), any(), any(), anyBoolean(),
|
||||
anyBoolean(), anyInt(), anyInt(), anyInt(), any());
|
||||
|
||||
doAnswer((invocation) -> {
|
||||
Log.v(TAG, "Intercepting scheduleRegisteredReceiver() for "
|
||||
+ Arrays.toString(invocation.getArguments()));
|
||||
final Intent intent = invocation.getArgument(1);
|
||||
final Bundle extras = invocation.getArgument(4);
|
||||
final boolean ordered = invocation.getArgument(5);
|
||||
mScheduledBroadcasts.add(makeScheduledBroadcast(r, intent));
|
||||
if (!wedge && ordered) {
|
||||
assertTrue(r.mReceivers.numberOfCurReceivers() > 0);
|
||||
assertNotEquals(ProcessList.SCHED_GROUP_UNDEFINED,
|
||||
mQueue.getPreferredSchedulingGroupLocked(r));
|
||||
mHandlerThread.getThreadHandler().post(() -> {
|
||||
synchronized (mAms) {
|
||||
mQueue.finishReceiverLocked(r, Activity.RESULT_OK,
|
||||
null, extrasOperator.apply(extras), abort, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}).when(thread).scheduleRegisteredReceiver(any(), any(), anyInt(), any(), any(),
|
||||
anyBoolean(), anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyInt(), any());
|
||||
|
||||
return r;
|
||||
}
|
||||
@@ -643,131 +625,6 @@ public class BroadcastQueueTest {
|
||||
};
|
||||
}
|
||||
|
||||
private static <T> boolean matchElement(T a, T b) {
|
||||
return a == null || a.equals(b);
|
||||
}
|
||||
private static <T> boolean matchObject(ArgumentMatcher<T> m, T b) {
|
||||
return m == null || m.matches(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an ArgumentMatcher for a manifest receiver. The parameters are in the order of
|
||||
* {@link IApplicationThread#scheduleReceiver} but the names correspond to the field names in
|
||||
* {@link ReceiverInfo}. For every parameter, a null means "don't care".
|
||||
*/
|
||||
private ArgumentMatcher<ReceiverInfo> manifestReceiverMatcher(
|
||||
ArgumentMatcher<Intent> intent,
|
||||
ArgumentMatcher<ActivityInfo> activityInfo,
|
||||
ArgumentMatcher<CompatibilityInfo> compatInfo,
|
||||
Integer resultCode,
|
||||
ArgumentMatcher<String> data,
|
||||
ArgumentMatcher<Bundle> extras,
|
||||
Boolean sync,
|
||||
Boolean assumeDelivered,
|
||||
Integer sendingUser,
|
||||
Integer processState) {
|
||||
return (test) -> {
|
||||
return test.registered == false
|
||||
&& matchObject(intent, test.intent)
|
||||
&& matchObject(activityInfo, test.activityInfo)
|
||||
&& matchObject(compatInfo, test.compatInfo)
|
||||
&& matchElement(resultCode, test.resultCode)
|
||||
&& matchObject(data, test.data)
|
||||
&& matchObject(extras, test.extras)
|
||||
&& matchElement(sync, test.sync)
|
||||
&& matchElement(assumeDelivered, test.assumeDelivered)
|
||||
&& matchElement(sendingUser, test.sendingUser)
|
||||
&& matchElement(processState, test.processState);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create an argument suitable for the verify() mock methods, when the goal is to find a call
|
||||
* containing a manifest receiver.
|
||||
*/
|
||||
private List<ReceiverInfo> manifestReceiver(
|
||||
ArgumentMatcher<Intent> intent,
|
||||
ArgumentMatcher<ActivityInfo> activityInfo,
|
||||
ArgumentMatcher<CompatibilityInfo> compatInfo,
|
||||
Integer resultCode,
|
||||
ArgumentMatcher<String> data,
|
||||
ArgumentMatcher<Bundle> extras,
|
||||
Boolean sync,
|
||||
Boolean assumeDelivered,
|
||||
Integer sendingUser,
|
||||
Integer processState) {
|
||||
return argThat(receiverList(manifestReceiverMatcher(intent, activityInfo, compatInfo,
|
||||
resultCode, data, extras, sync, assumeDelivered,
|
||||
sendingUser, processState)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an ArgumentMatcher for a registered receiver. The parameters are in the order of
|
||||
* {@link IApplicationThread#scheduleRegisteredReceiver} but the names correspond to the field
|
||||
* names in {@link ReceiverInfo}. For every parameter, a null means "don't care".
|
||||
*/
|
||||
private ArgumentMatcher<ReceiverInfo> registeredReceiverMatcher(
|
||||
ArgumentMatcher<IIntentReceiver> receiver,
|
||||
ArgumentMatcher<Intent> intent,
|
||||
Integer resultCode,
|
||||
ArgumentMatcher<String> data,
|
||||
ArgumentMatcher<Bundle> extras,
|
||||
Boolean ordered,
|
||||
Boolean sticky,
|
||||
Boolean assumeDelivered,
|
||||
Integer sendingUser,
|
||||
Integer processState) {
|
||||
return (test) -> {
|
||||
return test.registered == true
|
||||
&& matchObject(receiver, test.receiver)
|
||||
&& matchObject(intent, test.intent)
|
||||
&& matchElement(resultCode, test.resultCode)
|
||||
&& matchObject(data, test.data)
|
||||
&& matchObject(extras, test.extras)
|
||||
&& matchElement(ordered, test.ordered)
|
||||
&& matchElement(sticky, test.sticky)
|
||||
&& matchElement(assumeDelivered, test.assumeDelivered)
|
||||
&& matchElement(sendingUser, test.sendingUser)
|
||||
&& matchElement(processState, test.processState);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an argument suitable for the verify() mock methods, when the goal is to find a call
|
||||
* containing a registered receiver.
|
||||
*/
|
||||
private List<ReceiverInfo> registeredReceiver(
|
||||
ArgumentMatcher<IIntentReceiver> receiver,
|
||||
ArgumentMatcher<Intent> intent,
|
||||
Integer resultCode,
|
||||
ArgumentMatcher<String> data,
|
||||
ArgumentMatcher<Bundle> extras,
|
||||
Boolean ordered,
|
||||
Boolean sticky,
|
||||
Boolean assumeDelivered,
|
||||
Integer sendingUser,
|
||||
Integer processState) {
|
||||
return argThat(receiverList(registeredReceiverMatcher(receiver, intent, resultCode,
|
||||
data, extras, ordered, sticky, assumeDelivered,
|
||||
sendingUser, processState)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a matcher to every element in a ReceiverInfo list.
|
||||
*/
|
||||
private ArgumentMatcher<List<ReceiverInfo>> receiverList(ArgumentMatcher<ReceiverInfo> a) {
|
||||
return (test) -> {
|
||||
for (int i = 0; i < test.size(); i++) {
|
||||
ReceiverInfo r = test.get(i);
|
||||
if (a.matches(r)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
private ArgumentMatcher<Bundle> bundleEquals(Bundle bundle) {
|
||||
return (test) -> {
|
||||
// TODO: check values in addition to keys
|
||||
@@ -802,37 +659,42 @@ public class BroadcastQueueTest {
|
||||
ComponentName component) throws Exception {
|
||||
final Intent targetedIntent = new Intent(intent);
|
||||
targetedIntent.setComponent(component);
|
||||
verify(app.getThread(), mode).scheduleReceiverList(
|
||||
manifestReceiver(filterEquals(targetedIntent),
|
||||
null, null, null, null, null, null, null, UserHandle.USER_SYSTEM, null));
|
||||
verify(app.getThread(), mode).scheduleReceiver(
|
||||
argThat(filterEquals(targetedIntent)), any(), any(),
|
||||
anyInt(), any(), any(), eq(false), anyBoolean(), eq(UserHandle.USER_SYSTEM),
|
||||
anyInt(), anyInt(), any());
|
||||
}
|
||||
|
||||
private void verifyScheduleReceiver(VerificationMode mode, ProcessRecord app,
|
||||
Intent intent, int userId) throws Exception {
|
||||
verify(app.getThread(), mode).scheduleReceiverList(
|
||||
manifestReceiver(filterEqualsIgnoringComponent(intent),
|
||||
null, null, null, null, null, null, null, userId, null));
|
||||
verify(app.getThread(), mode).scheduleReceiver(
|
||||
argThat(filterEqualsIgnoringComponent(intent)), any(), any(),
|
||||
anyInt(), any(), any(), anyBoolean(), anyBoolean(), eq(userId),
|
||||
anyInt(), anyInt(), any());
|
||||
}
|
||||
|
||||
private void verifyScheduleReceiver(VerificationMode mode, ProcessRecord app,
|
||||
int userId) throws Exception {
|
||||
verify(app.getThread(), mode).scheduleReceiverList(
|
||||
manifestReceiver(null,
|
||||
null, null, null, null, null, null, null, userId, null));
|
||||
verify(app.getThread(), mode).scheduleReceiver(
|
||||
any(), any(), any(),
|
||||
anyInt(), any(), any(), anyBoolean(), anyBoolean(), eq(userId),
|
||||
anyInt(), anyInt(), any());
|
||||
}
|
||||
|
||||
private void verifyScheduleRegisteredReceiver(ProcessRecord app,
|
||||
Intent intent) throws Exception {
|
||||
verify(app.getThread()).scheduleReceiverList(
|
||||
registeredReceiver(null, filterEqualsIgnoringComponent(intent),
|
||||
null, null, null, null, null, null, UserHandle.USER_SYSTEM, null));
|
||||
verify(app.getThread()).scheduleRegisteredReceiver(
|
||||
any(), argThat(filterEqualsIgnoringComponent(intent)),
|
||||
anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyBoolean(),
|
||||
eq(UserHandle.USER_SYSTEM), anyInt(), anyInt(), any());
|
||||
}
|
||||
|
||||
private void verifyScheduleRegisteredReceiver(VerificationMode mode, ProcessRecord app,
|
||||
int userId) throws Exception {
|
||||
verify(app.getThread(), mode).scheduleReceiverList(
|
||||
registeredReceiver(null, null,
|
||||
null, null, null, null, null, null, userId, null));
|
||||
verify(app.getThread(), mode).scheduleRegisteredReceiver(
|
||||
any(), any(),
|
||||
anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyBoolean(),
|
||||
eq(userId), anyInt(), anyInt(), any());
|
||||
}
|
||||
|
||||
static final int USER_GUEST = 11;
|
||||
@@ -1485,24 +1347,25 @@ public class BroadcastQueueTest {
|
||||
final InOrder inOrder = inOrder(greenThread, blueThread, yellowThread, redThread);
|
||||
final Bundle expectedExtras = new Bundle();
|
||||
expectedExtras.putBoolean(PACKAGE_RED, true);
|
||||
inOrder.verify(greenThread).scheduleReceiverList(manifestReceiver(
|
||||
filterEqualsIgnoringComponent(airplane), null, null,
|
||||
Activity.RESULT_OK, null, bundleEquals(expectedExtras), true, false,
|
||||
UserHandle.USER_SYSTEM, null));
|
||||
inOrder.verify(blueThread).scheduleReceiverList(manifestReceiver(
|
||||
filterEqualsIgnoringComponent(airplane), null, null,
|
||||
Activity.RESULT_OK, null, bundleEquals(expectedExtras), true, false,
|
||||
UserHandle.USER_SYSTEM, null));
|
||||
inOrder.verify(greenThread).scheduleReceiver(
|
||||
argThat(filterEqualsIgnoringComponent(airplane)), any(), any(),
|
||||
eq(Activity.RESULT_OK), any(), argThat(bundleEquals(expectedExtras)),
|
||||
eq(true), eq(false), eq(UserHandle.USER_SYSTEM), anyInt(), anyInt(), any());
|
||||
inOrder.verify(blueThread).scheduleReceiver(
|
||||
argThat(filterEqualsIgnoringComponent(airplane)), any(), any(),
|
||||
eq(Activity.RESULT_OK), any(), argThat(bundleEquals(expectedExtras)),
|
||||
eq(true), eq(false), eq(UserHandle.USER_SYSTEM), anyInt(), anyInt(), any());
|
||||
expectedExtras.putBoolean(PACKAGE_BLUE, true);
|
||||
inOrder.verify(yellowThread).scheduleReceiverList(manifestReceiver(
|
||||
filterEqualsIgnoringComponent(airplane), null, null,
|
||||
Activity.RESULT_OK, null, bundleEquals(expectedExtras), true, false,
|
||||
UserHandle.USER_SYSTEM, null));
|
||||
inOrder.verify(yellowThread).scheduleReceiver(
|
||||
argThat(filterEqualsIgnoringComponent(airplane)), any(), any(),
|
||||
eq(Activity.RESULT_OK), any(), argThat(bundleEquals(expectedExtras)),
|
||||
eq(true), eq(false), eq(UserHandle.USER_SYSTEM), anyInt(), anyInt(), any());
|
||||
expectedExtras.putBoolean(PACKAGE_YELLOW, true);
|
||||
inOrder.verify(redThread).scheduleReceiverList(registeredReceiver(
|
||||
null, filterEquals(airplane),
|
||||
Activity.RESULT_OK, null, bundleEquals(expectedExtras), false,
|
||||
null, true, UserHandle.USER_SYSTEM, null));
|
||||
inOrder.verify(redThread).scheduleRegisteredReceiver(
|
||||
any(), argThat(filterEquals(airplane)),
|
||||
eq(Activity.RESULT_OK), any(), argThat(bundleEquals(expectedExtras)),
|
||||
eq(false), anyBoolean(), eq(true), eq(UserHandle.USER_SYSTEM), anyInt(),
|
||||
anyInt(), any());
|
||||
|
||||
// Finally, verify that we thawed the final receiver
|
||||
verify(mAms.mOomAdjuster.mCachedAppOptimizer).unfreezeTemporarily(eq(callerApp),
|
||||
@@ -1565,24 +1428,25 @@ public class BroadcastQueueTest {
|
||||
// have invoked or skipped the second receiver depending on the intent
|
||||
// flag policy; we always deliver to final receiver regardless of abort
|
||||
final InOrder inOrder = inOrder(greenThread, blueThread, redThread);
|
||||
inOrder.verify(greenThread).scheduleReceiverList(manifestReceiver(
|
||||
filterEqualsIgnoringComponent(intent), null, null,
|
||||
Activity.RESULT_OK, null, null, true, false, UserHandle.USER_SYSTEM,
|
||||
null));
|
||||
inOrder.verify(greenThread).scheduleReceiver(
|
||||
argThat(filterEqualsIgnoringComponent(intent)), any(), any(),
|
||||
eq(Activity.RESULT_OK), any(), any(), eq(true), eq(false),
|
||||
eq(UserHandle.USER_SYSTEM), anyInt(), anyInt(), any());
|
||||
if ((intent.getFlags() & Intent.FLAG_RECEIVER_NO_ABORT) != 0) {
|
||||
inOrder.verify(blueThread).scheduleReceiverList(manifestReceiver(
|
||||
filterEqualsIgnoringComponent(intent), null, null,
|
||||
Activity.RESULT_OK, null, null, true, false, UserHandle.USER_SYSTEM,
|
||||
null));
|
||||
inOrder.verify(blueThread).scheduleReceiver(
|
||||
argThat(filterEqualsIgnoringComponent(intent)), any(), any(),
|
||||
eq(Activity.RESULT_OK), any(), any(), eq(true), eq(false),
|
||||
eq(UserHandle.USER_SYSTEM), anyInt(), anyInt(), any());
|
||||
} else {
|
||||
inOrder.verify(blueThread, never()).scheduleReceiverList(manifestReceiver(
|
||||
null, null, null, null, null,
|
||||
null, null, null, null, null));
|
||||
inOrder.verify(blueThread, never()).scheduleReceiver(
|
||||
any(), any(), any(), anyInt(), any(), any(),
|
||||
anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyInt(), any());
|
||||
}
|
||||
inOrder.verify(redThread).scheduleReceiverList(registeredReceiver(
|
||||
null, filterEquals(intent),
|
||||
Activity.RESULT_OK, null, bundleEquals(expectedExtras),
|
||||
false, null, true, UserHandle.USER_SYSTEM, null));
|
||||
inOrder.verify(redThread).scheduleRegisteredReceiver(
|
||||
any(), argThat(filterEquals(intent)),
|
||||
eq(Activity.RESULT_OK), any(), argThat(bundleEquals(expectedExtras)),
|
||||
eq(false), anyBoolean(), eq(true), eq(UserHandle.USER_SYSTEM),
|
||||
anyInt(), anyInt(), any());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1602,10 +1466,11 @@ public class BroadcastQueueTest {
|
||||
orderedResultTo, orderedExtras));
|
||||
|
||||
waitForIdle();
|
||||
verify(callerThread).scheduleReceiverList(registeredReceiver(
|
||||
null, filterEquals(airplane),
|
||||
Activity.RESULT_OK, null, bundleEquals(orderedExtras), false,
|
||||
null, true, UserHandle.USER_SYSTEM, null));
|
||||
verify(callerThread).scheduleRegisteredReceiver(
|
||||
any(), argThat(filterEquals(airplane)),
|
||||
eq(Activity.RESULT_OK), any(), argThat(bundleEquals(orderedExtras)),
|
||||
eq(false), anyBoolean(), eq(true), eq(UserHandle.USER_SYSTEM),
|
||||
anyInt(), anyInt(), any());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1623,10 +1488,11 @@ public class BroadcastQueueTest {
|
||||
makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE)), resultTo));
|
||||
|
||||
waitForIdle();
|
||||
verify(callerThread).scheduleReceiverList(registeredReceiver(
|
||||
null, filterEquals(airplane),
|
||||
Activity.RESULT_OK, null, null, false,
|
||||
null, true, UserHandle.USER_SYSTEM, null));
|
||||
verify(callerThread).scheduleRegisteredReceiver(
|
||||
any(), argThat(filterEquals(airplane)),
|
||||
eq(Activity.RESULT_OK), any(), any(),
|
||||
eq(false), anyBoolean(), eq(true), eq(UserHandle.USER_SYSTEM),
|
||||
anyInt(), anyInt(), any());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1790,28 +1656,34 @@ public class BroadcastQueueTest {
|
||||
final InOrder inOrder = inOrder(callerThread, blueThread);
|
||||
|
||||
// First broadcast is canceled
|
||||
inOrder.verify(callerThread).scheduleReceiverList(registeredReceiver(null,
|
||||
filterAndExtrasEquals(timezoneFirst), Activity.RESULT_CANCELED, null,
|
||||
null, false, null, true, UserHandle.USER_SYSTEM, null));
|
||||
inOrder.verify(callerThread).scheduleRegisteredReceiver(
|
||||
any(), argThat(filterAndExtrasEquals(timezoneFirst)),
|
||||
eq(Activity.RESULT_CANCELED), any(), any(),
|
||||
eq(false), anyBoolean(), eq(true), eq(UserHandle.USER_SYSTEM),
|
||||
anyInt(), anyInt(), any());
|
||||
|
||||
// We deliver second broadcast to app
|
||||
timezoneSecond.setClassName(PACKAGE_BLUE, CLASS_GREEN);
|
||||
inOrder.verify(blueThread).scheduleReceiverList(manifestReceiver(
|
||||
filterAndExtrasEquals(timezoneSecond),
|
||||
null, null, null, null, null, true, false, null, null));
|
||||
inOrder.verify(blueThread).scheduleReceiver(
|
||||
argThat(filterAndExtrasEquals(timezoneSecond)), any(), any(),
|
||||
anyInt(), any(), any(), eq(true), eq(false), anyInt(),
|
||||
anyInt(), anyInt(), any());
|
||||
|
||||
// Second broadcast is finished
|
||||
timezoneSecond.setComponent(null);
|
||||
inOrder.verify(callerThread).scheduleReceiverList(registeredReceiver(null,
|
||||
filterAndExtrasEquals(timezoneSecond), Activity.RESULT_OK, null,
|
||||
null, false, null, true, UserHandle.USER_SYSTEM, null));
|
||||
inOrder.verify(callerThread).scheduleRegisteredReceiver(
|
||||
any(), argThat(filterAndExtrasEquals(timezoneSecond)),
|
||||
eq(Activity.RESULT_OK), any(), any(),
|
||||
eq(false), anyBoolean(), eq(true), eq(UserHandle.USER_SYSTEM),
|
||||
anyInt(), anyInt(), any());
|
||||
|
||||
// Since we "replaced" the first broadcast in its original position,
|
||||
// only now do we see the airplane broadcast
|
||||
airplane.setClassName(PACKAGE_BLUE, CLASS_RED);
|
||||
inOrder.verify(blueThread).scheduleReceiverList(manifestReceiver(
|
||||
filterEquals(airplane),
|
||||
null, null, null, null, null, false, false, null, null));
|
||||
inOrder.verify(blueThread).scheduleReceiver(
|
||||
argThat(filterEquals(airplane)), any(), any(),
|
||||
anyInt(), any(), any(), eq(false), eq(false), anyInt(),
|
||||
anyInt(), anyInt(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1873,15 +1745,17 @@ public class BroadcastQueueTest {
|
||||
|
||||
// First broadcast is delivered.
|
||||
timeTickFirst.setClassName(PACKAGE_BLUE, CLASS_BLUE);
|
||||
inOrder.verify(blueThread).scheduleReceiverList(manifestReceiver(
|
||||
filterAndExtrasEquals(timeTickFirst),
|
||||
null, null, null, null, null, false, false, null, null));
|
||||
inOrder.verify(blueThread).scheduleReceiver(
|
||||
argThat(filterAndExtrasEquals(timeTickFirst)), any(), any(),
|
||||
anyInt(), any(), any(), eq(false), eq(false), anyInt(),
|
||||
anyInt(), anyInt(), any());
|
||||
|
||||
// Second broadcast should be replaced by third broadcast.
|
||||
timeTickThird.setClassName(PACKAGE_BLUE, CLASS_BLUE);
|
||||
inOrder.verify(blueThread).scheduleReceiverList(manifestReceiver(
|
||||
filterAndExtrasEquals(timeTickThird),
|
||||
null, null, null, null, null, false, false, null, null));
|
||||
inOrder.verify(blueThread).scheduleReceiver(
|
||||
argThat(filterAndExtrasEquals(timeTickThird)), any(), any(),
|
||||
anyInt(), any(), any(), eq(false), eq(false), anyInt(),
|
||||
anyInt(), anyInt(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user