Merge changes from topic "sep26"
* changes: BroadcastQueue: ignore dead registered receivers. BroadcastQueue: more exception details. BroadcastQueue: max pending, misc fixes. BroadcastQueue: tests for failing starts, dead.
This commit is contained in:
@@ -136,6 +136,14 @@ public class BroadcastConstants {
|
||||
public int MAX_RUNNING_ACTIVE_BROADCASTS = DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS;
|
||||
private static final int DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS = 16;
|
||||
|
||||
/**
|
||||
* For {@link BroadcastQueueModernImpl}: Maximum number of pending
|
||||
* broadcasts to hold for a process before we ignore any delays that policy
|
||||
* might have applied to that process.
|
||||
*/
|
||||
public int MAX_PENDING_BROADCASTS = DEFAULT_MAX_PENDING_BROADCASTS;
|
||||
private static final int DEFAULT_MAX_PENDING_BROADCASTS = 256;
|
||||
|
||||
/**
|
||||
* For {@link BroadcastQueueModernImpl}: Default delay to apply to normal
|
||||
* broadcasts, giving a chance for debouncing of rapidly changing events.
|
||||
@@ -217,6 +225,8 @@ public class BroadcastConstants {
|
||||
DEFAULT_MAX_RUNNING_PROCESS_QUEUES);
|
||||
MAX_RUNNING_ACTIVE_BROADCASTS = properties.getInt("bcast_max_running_active_broadcasts",
|
||||
DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS);
|
||||
MAX_PENDING_BROADCASTS = properties.getInt("bcast_max_pending_broadcasts",
|
||||
DEFAULT_MAX_PENDING_BROADCASTS);
|
||||
DELAY_NORMAL_MILLIS = properties.getLong("bcast_delay_normal_millis",
|
||||
DEFAULT_DELAY_NORMAL_MILLIS);
|
||||
DELAY_CACHED_MILLIS = properties.getLong("bcast_delay_cached_millis",
|
||||
|
||||
@@ -119,6 +119,9 @@ class BroadcastProcessQueue {
|
||||
|
||||
private boolean mProcessCached;
|
||||
|
||||
private String mCachedToString;
|
||||
private String mCachedToShortString;
|
||||
|
||||
public BroadcastProcessQueue(@NonNull BroadcastConstants constants,
|
||||
@NonNull String processName, int uid) {
|
||||
this.constants = Objects.requireNonNull(constants);
|
||||
@@ -327,7 +330,7 @@ class BroadcastProcessQueue {
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return (mActive != null) && mPending.isEmpty();
|
||||
return mPending.isEmpty();
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
@@ -385,6 +388,12 @@ class BroadcastProcessQueue {
|
||||
} else {
|
||||
mRunnableAt = runnableAt + constants.DELAY_NORMAL_MILLIS;
|
||||
}
|
||||
|
||||
// If we have too many broadcasts pending, bypass any delays that
|
||||
// might have been applied above to aid draining
|
||||
if (mPending.size() >= constants.MAX_PENDING_BROADCASTS) {
|
||||
mRunnableAt = runnableAt;
|
||||
}
|
||||
} else {
|
||||
mRunnableAt = Long.MAX_VALUE;
|
||||
}
|
||||
@@ -452,13 +461,19 @@ class BroadcastProcessQueue {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BroadcastProcessQueue{"
|
||||
+ Integer.toHexString(System.identityHashCode(this))
|
||||
+ " " + processName + "/" + UserHandle.formatUid(uid) + "}";
|
||||
if (mCachedToString == null) {
|
||||
mCachedToString = "BroadcastProcessQueue{"
|
||||
+ Integer.toHexString(System.identityHashCode(this))
|
||||
+ " " + processName + "/" + UserHandle.formatUid(uid) + "}";
|
||||
}
|
||||
return mCachedToString;
|
||||
}
|
||||
|
||||
public String toShortString() {
|
||||
return processName + "/" + UserHandle.formatUid(uid);
|
||||
if (mCachedToShortString == null) {
|
||||
mCachedToShortString = processName + "/" + UserHandle.formatUid(uid);
|
||||
}
|
||||
return mCachedToShortString;
|
||||
}
|
||||
|
||||
public void dumpLocked(@NonNull IndentingPrintWriter pw) {
|
||||
|
||||
@@ -114,6 +114,9 @@ public abstract class BroadcastQueue {
|
||||
/**
|
||||
* Signal from OS internals that the given process has just been actively
|
||||
* attached, and is ready to begin receiving broadcasts.
|
||||
*
|
||||
* @return if the queue performed an action on the given process, such as
|
||||
* dispatching a pending broadcast
|
||||
*/
|
||||
@GuardedBy("mService")
|
||||
public abstract boolean onApplicationAttachedLocked(@NonNull ProcessRecord app);
|
||||
@@ -123,7 +126,7 @@ public abstract class BroadcastQueue {
|
||||
* an attempted start and attachment.
|
||||
*/
|
||||
@GuardedBy("mService")
|
||||
public abstract boolean onApplicationTimeoutLocked(@NonNull ProcessRecord app);
|
||||
public abstract void onApplicationTimeoutLocked(@NonNull ProcessRecord app);
|
||||
|
||||
/**
|
||||
* Signal from OS internals that the given process, which had already been
|
||||
@@ -131,14 +134,14 @@ public abstract class BroadcastQueue {
|
||||
* not responding.
|
||||
*/
|
||||
@GuardedBy("mService")
|
||||
public abstract boolean onApplicationProblemLocked(@NonNull ProcessRecord app);
|
||||
public abstract void onApplicationProblemLocked(@NonNull ProcessRecord app);
|
||||
|
||||
/**
|
||||
* Signal from OS internals that the given process has been killed, and is
|
||||
* no longer actively running.
|
||||
*/
|
||||
@GuardedBy("mService")
|
||||
public abstract boolean onApplicationCleanupLocked(@NonNull ProcessRecord app);
|
||||
public abstract void onApplicationCleanupLocked(@NonNull ProcessRecord app);
|
||||
|
||||
/**
|
||||
* Signal from OS internals that the given package (or some subset of that
|
||||
|
||||
@@ -426,16 +426,16 @@ public class BroadcastQueueImpl extends BroadcastQueue {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onApplicationTimeoutLocked(ProcessRecord app) {
|
||||
return skipCurrentOrPendingReceiverLocked(app);
|
||||
public void onApplicationTimeoutLocked(ProcessRecord app) {
|
||||
skipCurrentOrPendingReceiverLocked(app);
|
||||
}
|
||||
|
||||
public boolean onApplicationProblemLocked(ProcessRecord app) {
|
||||
return skipCurrentOrPendingReceiverLocked(app);
|
||||
public void onApplicationProblemLocked(ProcessRecord app) {
|
||||
skipCurrentOrPendingReceiverLocked(app);
|
||||
}
|
||||
|
||||
public boolean onApplicationCleanupLocked(ProcessRecord app) {
|
||||
return skipCurrentOrPendingReceiverLocked(app);
|
||||
public void onApplicationCleanupLocked(ProcessRecord app) {
|
||||
skipCurrentOrPendingReceiverLocked(app);
|
||||
}
|
||||
|
||||
public boolean sendPendingBroadcastsLocked(ProcessRecord app) {
|
||||
@@ -732,9 +732,10 @@ public class BroadcastQueueImpl extends BroadcastQueue {
|
||||
} catch (RemoteException ex) {
|
||||
// Failed to call into the process. It's either dying or wedged. Kill it gently.
|
||||
synchronized (mService) {
|
||||
Slog.w(TAG, "Can't deliver broadcast to " + app.processName
|
||||
+ " (pid " + app.getPid() + "). Crashing it.");
|
||||
app.scheduleCrashLocked("can't deliver broadcast",
|
||||
final String msg = "Failed to schedule " + intent + " to " + receiver
|
||||
+ " via " + app + ": " + ex;
|
||||
Slog.w(TAG, msg);
|
||||
app.scheduleCrashLocked(msg,
|
||||
CannotDeliverBroadcastException.TYPE_ID, /* extras=*/ null);
|
||||
}
|
||||
throw ex;
|
||||
@@ -814,7 +815,11 @@ public class BroadcastQueueImpl extends BroadcastQueue {
|
||||
try {
|
||||
if (DEBUG_BROADCAST_LIGHT) Slog.i(TAG_BROADCAST,
|
||||
"Delivering to " + filter + " : " + r);
|
||||
if (filter.receiverList.app != null && filter.receiverList.app.isInFullBackup()) {
|
||||
final boolean isInFullBackup = (filter.receiverList.app != null)
|
||||
&& filter.receiverList.app.isInFullBackup();
|
||||
final boolean isKilled = (filter.receiverList.app != null)
|
||||
&& filter.receiverList.app.isKilled();
|
||||
if (isInFullBackup || isKilled) {
|
||||
// Skip delivery if full backup in progress
|
||||
// If it's an ordered broadcast, we need to continue to the next receiver.
|
||||
if (ordered) {
|
||||
@@ -1379,8 +1384,11 @@ public class BroadcastQueueImpl extends BroadcastQueue {
|
||||
processCurBroadcastLocked(r, app);
|
||||
return;
|
||||
} catch (RemoteException e) {
|
||||
Slog.w(TAG, "Exception when sending broadcast to "
|
||||
+ r.curComponent, e);
|
||||
final String msg = "Failed to schedule " + r.intent + " to " + info
|
||||
+ " via " + app + ": " + e;
|
||||
Slog.w(TAG, msg);
|
||||
app.scheduleCrashLocked(msg,
|
||||
CannotDeliverBroadcastException.TYPE_ID, /* extras=*/ null);
|
||||
} catch (RuntimeException e) {
|
||||
Slog.wtf(TAG, "Failed sending broadcast to "
|
||||
+ r.curComponent + " with " + r.intent, e);
|
||||
|
||||
@@ -290,7 +290,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
}
|
||||
|
||||
// If app isn't running, and there's nothing in the queue, clean up
|
||||
if (queue.isEmpty() && !queue.isProcessWarm()) {
|
||||
if (queue.isEmpty() && !queue.isActive() && !queue.isProcessWarm()) {
|
||||
removeProcessQueue(queue.processName, queue.uid);
|
||||
}
|
||||
}
|
||||
@@ -420,18 +420,17 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onApplicationTimeoutLocked(@NonNull ProcessRecord app) {
|
||||
return onApplicationCleanupLocked(app);
|
||||
public void onApplicationTimeoutLocked(@NonNull ProcessRecord app) {
|
||||
onApplicationCleanupLocked(app);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onApplicationProblemLocked(@NonNull ProcessRecord app) {
|
||||
return onApplicationCleanupLocked(app);
|
||||
public void onApplicationProblemLocked(@NonNull ProcessRecord app) {
|
||||
onApplicationCleanupLocked(app);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onApplicationCleanupLocked(@NonNull ProcessRecord app) {
|
||||
boolean didSomething = false;
|
||||
public void onApplicationCleanupLocked(@NonNull ProcessRecord app) {
|
||||
if ((mRunningColdStart != null) && (mRunningColdStart.app == app)) {
|
||||
// We've been waiting for this app to cold start, and it had
|
||||
// trouble; clear the slot and fail delivery below
|
||||
@@ -439,7 +438,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
|
||||
// We might be willing to kick off another cold start
|
||||
enqueueUpdateRunningList();
|
||||
didSomething = true;
|
||||
}
|
||||
|
||||
final BroadcastProcessQueue queue = getProcessQueue(app);
|
||||
@@ -449,16 +447,19 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
// If queue was running a broadcast, fail it
|
||||
if (queue.isActive()) {
|
||||
finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
|
||||
didSomething = true;
|
||||
}
|
||||
|
||||
// Skip any pending registered receivers, since the old process
|
||||
// would never be around to receive them
|
||||
queue.removeMatchingBroadcasts((r, i) -> {
|
||||
return (r.receivers.get(i) instanceof BroadcastFilter);
|
||||
}, mBroadcastConsumerSkip);
|
||||
|
||||
// If queue has nothing else pending, consider cleaning it
|
||||
if (queue.isEmpty()) {
|
||||
updateRunnableList(queue);
|
||||
}
|
||||
}
|
||||
|
||||
return didSomething;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -515,6 +516,13 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
final int index = queue.getActiveIndex();
|
||||
final Object receiver = r.receivers.get(index);
|
||||
|
||||
// Ignore registered receivers from a previous PID
|
||||
if (receiver instanceof BroadcastFilter) {
|
||||
mRunningColdStart = null;
|
||||
finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
|
||||
return;
|
||||
}
|
||||
|
||||
final ApplicationInfo info = ((ResolveInfo) receiver).activityInfo.applicationInfo;
|
||||
final ComponentName component = ((ResolveInfo) receiver).activityInfo.getComponentName();
|
||||
|
||||
@@ -536,6 +544,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
} else {
|
||||
mRunningColdStart = null;
|
||||
finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -563,7 +572,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
return;
|
||||
}
|
||||
|
||||
// Consider additional cases where we'd want fo finish immediately
|
||||
// Consider additional cases where we'd want to finish immediately
|
||||
if (app.isInFullBackup()) {
|
||||
finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
|
||||
return;
|
||||
@@ -578,7 +587,14 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!r.timeoutExempt) {
|
||||
// Ignore registered receivers from a previous PID
|
||||
if ((receiver instanceof BroadcastFilter)
|
||||
&& ((BroadcastFilter) receiver).receiverList.pid != app.getPid()) {
|
||||
finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mService.mProcessesReady && !r.timeoutExempt) {
|
||||
final long timeout = r.isForeground() ? mFgConstants.TIMEOUT : mBgConstants.TIMEOUT;
|
||||
mLocalHandler.sendMessageDelayed(
|
||||
Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT, queue), timeout);
|
||||
@@ -604,7 +620,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
}
|
||||
|
||||
if (DEBUG_BROADCAST) logv("Scheduling " + r + " to warm " + app);
|
||||
setDeliveryState(queue, r, index, receiver, BroadcastRecord.DELIVERY_SCHEDULED);
|
||||
setDeliveryState(queue, app, r, index, receiver, BroadcastRecord.DELIVERY_SCHEDULED);
|
||||
|
||||
final IApplicationThread thread = app.getThread();
|
||||
if (thread != null) {
|
||||
@@ -628,8 +644,12 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
app.mState.getReportedProcState());
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
final String msg = "Failed to schedule " + r + " to " + receiver
|
||||
+ " via " + app + ": " + e;
|
||||
Slog.w(TAG, msg);
|
||||
app.scheduleCrashLocked(msg, CannotDeliverBroadcastException.TYPE_ID, null);
|
||||
app.setKilled(true);
|
||||
finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
|
||||
app.scheduleCrashLocked(TAG, CannotDeliverBroadcastException.TYPE_ID, null);
|
||||
}
|
||||
} else {
|
||||
finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
|
||||
@@ -652,7 +672,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
r.resultCode, r.resultData, r.resultExtras, false, r.initialSticky,
|
||||
r.userId, app.mState.getReportedProcState());
|
||||
} catch (RemoteException e) {
|
||||
app.scheduleCrashLocked(TAG, CannotDeliverBroadcastException.TYPE_ID, null);
|
||||
final String msg = "Failed to schedule result of " + r + " via " + app + ": " + e;
|
||||
Slog.w(TAG, msg);
|
||||
app.scheduleCrashLocked(msg, CannotDeliverBroadcastException.TYPE_ID, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -674,7 +696,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
// receivers as skipped
|
||||
if (r.ordered && r.resultAbort) {
|
||||
for (int i = r.finishedCount + 1; i < r.receivers.size(); i++) {
|
||||
setDeliveryState(null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED);
|
||||
setDeliveryState(null, null, r, i, r.receivers.get(i),
|
||||
BroadcastRecord.DELIVERY_SKIPPED);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -690,9 +713,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
final int index = queue.getActiveIndex();
|
||||
final Object receiver = r.receivers.get(index);
|
||||
|
||||
setDeliveryState(queue, r, index, receiver, deliveryState);
|
||||
setDeliveryState(queue, app, r, index, receiver, deliveryState);
|
||||
|
||||
if (deliveryState == BroadcastRecord.DELIVERY_TIMEOUT) {
|
||||
r.anrCount++;
|
||||
if (app != null && !app.isDebugging()) {
|
||||
mService.appNotResponding(queue.app, TimeoutRecord
|
||||
.forBroadcastReceiver("Broadcast of " + r.toShortString()));
|
||||
@@ -704,7 +728,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
// Even if we have more broadcasts, if we've made reasonable progress
|
||||
// and someone else is waiting, retire ourselves to avoid starvation
|
||||
final boolean shouldRetire = (mRunnableHead != null)
|
||||
&& (queue.getActiveCountSinceIdle() > mConstants.MAX_RUNNING_ACTIVE_BROADCASTS);
|
||||
&& (queue.getActiveCountSinceIdle() >= mConstants.MAX_RUNNING_ACTIVE_BROADCASTS);
|
||||
|
||||
if (queue.isRunnable() && queue.isProcessWarm() && !shouldRetire) {
|
||||
// We're on a roll; move onto the next broadcast for this process
|
||||
@@ -733,17 +757,22 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
* bookkeeping related to ordered broadcasts.
|
||||
*/
|
||||
private void setDeliveryState(@Nullable BroadcastProcessQueue queue,
|
||||
@NonNull BroadcastRecord r, int index, @NonNull Object receiver,
|
||||
@DeliveryState int newDeliveryState) {
|
||||
@Nullable ProcessRecord app, @NonNull BroadcastRecord r, int index,
|
||||
@NonNull Object receiver, @DeliveryState int newDeliveryState) {
|
||||
final int oldDeliveryState = getDeliveryState(r, index);
|
||||
|
||||
if (newDeliveryState != BroadcastRecord.DELIVERY_DELIVERED) {
|
||||
Slog.w(TAG, "Delivery state of " + r + " to " + receiver + " changed from "
|
||||
Slog.w(TAG, "Delivery state of " + r + " to " + receiver
|
||||
+ " via " + app + " changed from "
|
||||
+ deliveryStateToString(oldDeliveryState) + " to "
|
||||
+ deliveryStateToString(newDeliveryState));
|
||||
}
|
||||
|
||||
r.setDeliveryState(index, newDeliveryState);
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Emit any relevant tracing results when we're changing the delivery
|
||||
// state as part of running from a queue
|
||||
@@ -837,7 +866,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
* of it matching a predicate.
|
||||
*/
|
||||
private final BroadcastConsumer mBroadcastConsumerSkip = (r, i) -> {
|
||||
setDeliveryState(null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED);
|
||||
setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED);
|
||||
};
|
||||
|
||||
private boolean skipMatchingBroadcasts(
|
||||
|
||||
@@ -128,6 +128,9 @@ final class BroadcastRecord extends Binder {
|
||||
@Nullable
|
||||
final BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver;
|
||||
|
||||
String cachedToString;
|
||||
String cachedToShortString;
|
||||
|
||||
static final int IDLE = 0;
|
||||
static final int APP_RECEIVE = 1;
|
||||
static final int CALL_IN_RECEIVE = 2;
|
||||
@@ -700,21 +703,27 @@ final class BroadcastRecord extends Binder {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String label = intent.getAction();
|
||||
if (label == null) {
|
||||
label = intent.toString();
|
||||
if (cachedToString == null) {
|
||||
String label = intent.getAction();
|
||||
if (label == null) {
|
||||
label = intent.toString();
|
||||
}
|
||||
cachedToString = "BroadcastRecord{"
|
||||
+ Integer.toHexString(System.identityHashCode(this))
|
||||
+ " u" + userId + " " + label + "}";
|
||||
}
|
||||
return "BroadcastRecord{"
|
||||
+ Integer.toHexString(System.identityHashCode(this))
|
||||
+ " u" + userId + " " + label + "}";
|
||||
return cachedToString;
|
||||
}
|
||||
|
||||
public String toShortString() {
|
||||
String label = intent.getAction();
|
||||
if (label == null) {
|
||||
label = intent.toString();
|
||||
if (cachedToShortString == null) {
|
||||
String label = intent.getAction();
|
||||
if (label == null) {
|
||||
label = intent.toString();
|
||||
}
|
||||
cachedToShortString = label + "/u" + userId;
|
||||
}
|
||||
return label + "/u" + userId;
|
||||
return cachedToShortString;
|
||||
}
|
||||
|
||||
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.server.am;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
@@ -42,6 +43,7 @@ import android.app.ActivityManager;
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.BroadcastOptions;
|
||||
import android.app.IApplicationThread;
|
||||
import android.app.RemoteServiceException.CannotDeliverBroadcastException;
|
||||
import android.app.usage.UsageEvents.Event;
|
||||
import android.app.usage.UsageStatsManagerInternal;
|
||||
import android.content.ComponentName;
|
||||
@@ -56,6 +58,7 @@ import android.content.pm.PackageManagerInternal;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.DeadObjectException;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.IBinder;
|
||||
@@ -135,6 +138,12 @@ public class BroadcastQueueTest {
|
||||
private ActivityManagerService mAms;
|
||||
private BroadcastQueue mQueue;
|
||||
|
||||
/**
|
||||
* When enabled {@link ActivityManagerService#startProcessLocked} will fail
|
||||
* by returning {@code null}; otherwise it will spawn a new mock process.
|
||||
*/
|
||||
private boolean mFailStartProcess;
|
||||
|
||||
/**
|
||||
* Map from PID to registered registered runtime receivers.
|
||||
*/
|
||||
@@ -185,10 +194,13 @@ public class BroadcastQueueTest {
|
||||
doAnswer((invocation) -> {
|
||||
Log.v(TAG, "Intercepting startProcessLocked() for "
|
||||
+ Arrays.toString(invocation.getArguments()));
|
||||
if (mFailStartProcess) {
|
||||
return null;
|
||||
}
|
||||
final String processName = invocation.getArgument(0);
|
||||
final ApplicationInfo ai = invocation.getArgument(1);
|
||||
final ProcessRecord res = makeActiveProcessRecord(ai, processName, false,
|
||||
false, UnaryOperator.identity());
|
||||
final ProcessRecord res = makeActiveProcessRecord(ai, processName,
|
||||
ProcessBehavior.NORMAL, UnaryOperator.identity());
|
||||
mHandlerThread.getThreadHandler().post(() -> {
|
||||
synchronized (mAms) {
|
||||
mQueue.onApplicationAttachedLocked(res);
|
||||
@@ -197,6 +209,16 @@ public class BroadcastQueueTest {
|
||||
return res;
|
||||
}).when(mAms).startProcessLocked(any(), any(), anyBoolean(), anyInt(),
|
||||
any(), anyInt(), anyBoolean(), anyBoolean());
|
||||
doAnswer((invocation) -> {
|
||||
final String processName = invocation.getArgument(0);
|
||||
final int uid = invocation.getArgument(1);
|
||||
for (ProcessRecord r : mActiveProcesses) {
|
||||
if (Objects.equals(r.processName, processName) && r.uid == uid) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}).when(mAms).getProcessRecordLocked(any(), anyInt());
|
||||
doNothing().when(mAms).appNotResponding(any(), any());
|
||||
|
||||
final BroadcastConstants constants = new BroadcastConstants(
|
||||
@@ -278,29 +300,51 @@ public class BroadcastQueueTest {
|
||||
}
|
||||
}
|
||||
|
||||
private enum ProcessBehavior {
|
||||
/** Process broadcasts normally */
|
||||
NORMAL,
|
||||
/** Wedge and never confirm broadcast receipt */
|
||||
WEDGE,
|
||||
/** Process broadcast by requesting abort */
|
||||
ABORT,
|
||||
/** Appear to behave completely dead */
|
||||
DEAD,
|
||||
}
|
||||
|
||||
private ProcessRecord makeActiveProcessRecord(String packageName) throws Exception {
|
||||
final ApplicationInfo ai = makeApplicationInfo(packageName);
|
||||
return makeActiveProcessRecord(ai, ai.processName, false, false,
|
||||
return makeActiveProcessRecord(ai, ai.processName, ProcessBehavior.NORMAL,
|
||||
UnaryOperator.identity());
|
||||
}
|
||||
|
||||
private ProcessRecord makeWedgedActiveProcessRecord(String packageName) throws Exception {
|
||||
private ProcessRecord makeActiveProcessRecord(String packageName,
|
||||
ProcessBehavior behavior) throws Exception {
|
||||
final ApplicationInfo ai = makeApplicationInfo(packageName);
|
||||
return makeActiveProcessRecord(ai, ai.processName, true, false,
|
||||
return makeActiveProcessRecord(ai, ai.processName, behavior,
|
||||
UnaryOperator.identity());
|
||||
}
|
||||
|
||||
private ProcessRecord makeActiveProcessRecord(ApplicationInfo ai, String processName,
|
||||
boolean wedged, boolean abort, UnaryOperator<Bundle> extrasOperator) throws Exception {
|
||||
ProcessBehavior behavior, UnaryOperator<Bundle> extrasOperator) throws Exception {
|
||||
final boolean wedge = (behavior == ProcessBehavior.WEDGE);
|
||||
final boolean abort = (behavior == ProcessBehavior.ABORT);
|
||||
final boolean dead = (behavior == ProcessBehavior.DEAD);
|
||||
|
||||
final ProcessRecord r = spy(new ProcessRecord(mAms, ai, processName, ai.uid));
|
||||
r.setPid(mNextPid.getAndIncrement());
|
||||
mActiveProcesses.add(r);
|
||||
|
||||
final IApplicationThread thread = mock(IApplicationThread.class);
|
||||
final IApplicationThread thread;
|
||||
if (dead) {
|
||||
thread = mock(IApplicationThread.class, (invocation) -> {
|
||||
throw new DeadObjectException();
|
||||
});
|
||||
} else {
|
||||
thread = mock(IApplicationThread.class);
|
||||
}
|
||||
final IBinder threadBinder = new Binder();
|
||||
doReturn(threadBinder).when(thread).asBinder();
|
||||
r.makeActive(thread, mAms.mProcessStats);
|
||||
doReturn(r).when(mAms).getProcessRecordLocked(eq(r.info.processName), eq(r.info.uid));
|
||||
|
||||
final IIntentReceiver receiver = mock(IIntentReceiver.class);
|
||||
final IBinder receiverBinder = new Binder();
|
||||
@@ -309,18 +353,29 @@ public class BroadcastQueueTest {
|
||||
UserHandle.getUserId(r.info.uid), receiver);
|
||||
mRegisteredReceivers.put(r.getPid(), receiverList);
|
||||
|
||||
doAnswer((invocation) -> {
|
||||
Log.v(TAG, "Intercepting killLocked() for "
|
||||
+ Arrays.toString(invocation.getArguments()));
|
||||
mActiveProcesses.remove(r);
|
||||
mRegisteredReceivers.remove(r.getPid());
|
||||
return invocation.callRealMethod();
|
||||
}).when(r).killLocked(any(), any(), anyInt(), anyInt(), anyBoolean());
|
||||
|
||||
// If we're entirely dead, rely on default behaviors above
|
||||
if (dead) return r;
|
||||
|
||||
doAnswer((invocation) -> {
|
||||
Log.v(TAG, "Intercepting scheduleReceiver() for "
|
||||
+ Arrays.toString(invocation.getArguments()));
|
||||
final Bundle extras = invocation.getArgument(5);
|
||||
if (!wedged) {
|
||||
if (!wedge) {
|
||||
assertTrue(r.mReceivers.numberOfCurReceivers() > 0);
|
||||
assertTrue(mQueue.getPreferredSchedulingGroupLocked(r)
|
||||
!= ProcessList.SCHED_GROUP_UNDEFINED);
|
||||
mHandlerThread.getThreadHandler().post(() -> {
|
||||
synchronized (mAms) {
|
||||
mQueue.finishReceiverLocked(r, Activity.RESULT_OK,
|
||||
null, extrasOperator.apply(extras), abort, false);
|
||||
mQueue.finishReceiverLocked(r, Activity.RESULT_OK, null,
|
||||
extrasOperator.apply(extras), abort, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -333,7 +388,7 @@ public class BroadcastQueueTest {
|
||||
+ Arrays.toString(invocation.getArguments()));
|
||||
final Bundle extras = invocation.getArgument(4);
|
||||
final boolean ordered = invocation.getArgument(5);
|
||||
if (!wedged && ordered) {
|
||||
if (!wedge && ordered) {
|
||||
assertTrue(r.mReceivers.numberOfCurReceivers() > 0);
|
||||
assertTrue(mQueue.getPreferredSchedulingGroupLocked(r)
|
||||
!= ProcessList.SCHED_GROUP_UNDEFINED);
|
||||
@@ -449,6 +504,13 @@ public class BroadcastQueueTest {
|
||||
any(), eq(false), eq(UserHandle.USER_SYSTEM), anyInt());
|
||||
}
|
||||
|
||||
private void verifyScheduleReceiver(VerificationMode mode, ProcessRecord app, Intent intent)
|
||||
throws Exception {
|
||||
verify(app.getThread(), mode).scheduleReceiver(
|
||||
argThat(filterEqualsIgnoringComponent(intent)), any(), any(), anyInt(), any(),
|
||||
any(), eq(false), eq(UserHandle.USER_SYSTEM), anyInt());
|
||||
}
|
||||
|
||||
private void verifyScheduleReceiver(VerificationMode mode, ProcessRecord app, Intent intent,
|
||||
ComponentName component) throws Exception {
|
||||
final Intent targetedIntent = new Intent(intent);
|
||||
@@ -689,7 +751,8 @@ public class BroadcastQueueTest {
|
||||
@Test
|
||||
public void testWedged() throws Exception {
|
||||
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
|
||||
final ProcessRecord receiverApp = makeWedgedActiveProcessRecord(PACKAGE_GREEN);
|
||||
final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN,
|
||||
ProcessBehavior.WEDGE);
|
||||
|
||||
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
|
||||
enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
|
||||
@@ -699,6 +762,106 @@ public class BroadcastQueueTest {
|
||||
verify(mAms).appNotResponding(eq(receiverApp), any());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that we handle registered receivers in a process that always
|
||||
* responds with {@link DeadObjectException}, recovering to restart the
|
||||
* process and deliver their next broadcast.
|
||||
*/
|
||||
@Test
|
||||
public void testDead_Registered() throws Exception {
|
||||
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
|
||||
final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN,
|
||||
ProcessBehavior.DEAD);
|
||||
|
||||
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
|
||||
enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
|
||||
List.of(makeRegisteredReceiver(receiverApp))));
|
||||
final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
|
||||
enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
|
||||
List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN))));
|
||||
waitForIdle();
|
||||
|
||||
// First broadcast should have already been dead
|
||||
verifyScheduleRegisteredReceiver(receiverApp, airplane);
|
||||
verify(receiverApp).scheduleCrashLocked(any(),
|
||||
eq(CannotDeliverBroadcastException.TYPE_ID), any());
|
||||
|
||||
// Second broadcast in new process should work fine
|
||||
final ProcessRecord restartedReceiverApp = mAms.getProcessRecordLocked(PACKAGE_GREEN,
|
||||
getUidForPackage(PACKAGE_GREEN));
|
||||
assertNotEquals(receiverApp, restartedReceiverApp);
|
||||
verifyScheduleReceiver(restartedReceiverApp, timezone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that we handle manifest receivers in a process that always
|
||||
* responds with {@link DeadObjectException}, recovering to restart the
|
||||
* process and deliver their next broadcast.
|
||||
*/
|
||||
@Test
|
||||
public void testDead_Manifest() throws Exception {
|
||||
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
|
||||
final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN,
|
||||
ProcessBehavior.DEAD);
|
||||
|
||||
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
|
||||
enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
|
||||
List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN))));
|
||||
final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
|
||||
enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
|
||||
List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN))));
|
||||
waitForIdle();
|
||||
|
||||
// First broadcast should have already been dead
|
||||
verifyScheduleReceiver(receiverApp, airplane);
|
||||
verify(receiverApp).scheduleCrashLocked(any(),
|
||||
eq(CannotDeliverBroadcastException.TYPE_ID), any());
|
||||
|
||||
// Second broadcast in new process should work fine
|
||||
final ProcessRecord restartedReceiverApp = mAms.getProcessRecordLocked(PACKAGE_GREEN,
|
||||
getUidForPackage(PACKAGE_GREEN));
|
||||
assertNotEquals(receiverApp, restartedReceiverApp);
|
||||
verifyScheduleReceiver(restartedReceiverApp, timezone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that we handle the system failing to start a process.
|
||||
*/
|
||||
@Test
|
||||
public void testFailStartProcess() throws Exception {
|
||||
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
|
||||
|
||||
// Send broadcast while process starts are failing
|
||||
mFailStartProcess = true;
|
||||
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
|
||||
enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
|
||||
List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
|
||||
makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW))));
|
||||
|
||||
// Confirm that queue goes idle, with no processes
|
||||
waitForIdle();
|
||||
assertEquals(1, mActiveProcesses.size());
|
||||
|
||||
// Send more broadcasts with working process starts
|
||||
mFailStartProcess = false;
|
||||
final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
|
||||
enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
|
||||
List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
|
||||
makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW))));
|
||||
|
||||
// Confirm that we only saw second broadcast
|
||||
waitForIdle();
|
||||
assertEquals(3, mActiveProcesses.size());
|
||||
final ProcessRecord receiverGreenApp = mAms.getProcessRecordLocked(PACKAGE_GREEN,
|
||||
getUidForPackage(PACKAGE_GREEN));
|
||||
final ProcessRecord receiverYellowApp = mAms.getProcessRecordLocked(PACKAGE_YELLOW,
|
||||
getUidForPackage(PACKAGE_YELLOW));
|
||||
verifyScheduleReceiver(never(), receiverGreenApp, airplane);
|
||||
verifyScheduleReceiver(never(), receiverYellowApp, airplane);
|
||||
verifyScheduleReceiver(times(1), receiverGreenApp, timezone);
|
||||
verifyScheduleReceiver(times(1), receiverYellowApp, timezone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that we cleanup a disabled component, skipping a pending dispatch
|
||||
* of broadcast to that component.
|
||||
@@ -741,6 +904,45 @@ public class BroadcastQueueTest {
|
||||
new ComponentName(PACKAGE_GREEN, CLASS_BLUE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that killing a running process skips registered receivers.
|
||||
*/
|
||||
@Test
|
||||
public void testKill() throws Exception {
|
||||
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
|
||||
final ProcessRecord oldApp = makeActiveProcessRecord(PACKAGE_GREEN);
|
||||
|
||||
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
|
||||
try (SyncBarrier b = new SyncBarrier()) {
|
||||
enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, new ArrayList<>(
|
||||
List.of(makeRegisteredReceiver(oldApp),
|
||||
makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)))));
|
||||
|
||||
synchronized (mAms) {
|
||||
oldApp.killLocked(TAG, 42, false);
|
||||
mQueue.onApplicationCleanupLocked(oldApp);
|
||||
}
|
||||
}
|
||||
waitForIdle();
|
||||
|
||||
// Confirm that we cold-started after the kill
|
||||
final ProcessRecord newApp = mAms.getProcessRecordLocked(PACKAGE_GREEN,
|
||||
getUidForPackage(PACKAGE_GREEN));
|
||||
assertNotEquals(oldApp, newApp);
|
||||
|
||||
// Confirm that we saw no registered receiver traffic
|
||||
final IApplicationThread oldThread = oldApp.getThread();
|
||||
verify(oldThread, never()).scheduleRegisteredReceiver(any(),
|
||||
any(), anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyInt(), anyInt());
|
||||
final IApplicationThread newThread = newApp.getThread();
|
||||
verify(newThread, never()).scheduleRegisteredReceiver(any(),
|
||||
any(), anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyInt(), anyInt());
|
||||
|
||||
// Confirm that we saw final manifest broadcast
|
||||
verifyScheduleReceiver(times(1), newApp, airplane,
|
||||
new ComponentName(PACKAGE_GREEN, CLASS_GREEN));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that we skip broadcasts to an app being backed up.
|
||||
*/
|
||||
@@ -770,13 +972,13 @@ public class BroadcastQueueTest {
|
||||
// Purposefully warm-start the middle apps to make sure we dispatch to
|
||||
// both cold and warm apps in expected order
|
||||
makeActiveProcessRecord(makeApplicationInfo(PACKAGE_BLUE), PACKAGE_BLUE,
|
||||
false, false, (extras) -> {
|
||||
ProcessBehavior.NORMAL, (extras) -> {
|
||||
extras = clone(extras);
|
||||
extras.putBoolean(PACKAGE_BLUE, true);
|
||||
return extras;
|
||||
});
|
||||
makeActiveProcessRecord(makeApplicationInfo(PACKAGE_YELLOW), PACKAGE_YELLOW,
|
||||
false, false, (extras) -> {
|
||||
ProcessBehavior.NORMAL, (extras) -> {
|
||||
extras = clone(extras);
|
||||
extras.putBoolean(PACKAGE_YELLOW, true);
|
||||
return extras;
|
||||
@@ -858,7 +1060,7 @@ public class BroadcastQueueTest {
|
||||
|
||||
// Create a process that aborts any ordered broadcasts
|
||||
makeActiveProcessRecord(makeApplicationInfo(PACKAGE_GREEN), PACKAGE_GREEN,
|
||||
false, true, (extras) -> {
|
||||
ProcessBehavior.ABORT, (extras) -> {
|
||||
extras = clone(extras);
|
||||
extras.putBoolean(PACKAGE_GREEN, true);
|
||||
return extras;
|
||||
|
||||
Reference in New Issue
Block a user