BroadcastQueue: remove matching broadcasts.

There are common broadcasts that should remove any still-pending
broadcasts of a particular flavor.  For example, sending SCREEN_ON
should remove any still-pending SCREEN_OFF broadcasts.

This change adds a BroadcastOption to support this use-case, along
with tests to validate, and starts wiring up the option to a handful
of straightforward broadcasts.

For this to work effectively, we need to actually remove skipped
events from our per-process queues, which means we need a more
robust way of finishing ordered broadcasts.  This change factors
out the "finishing" of each broadcast to a local setDeliveryState()
method which can then be invoked when a broadcast is removed.

Bug: 245771249
Test: atest FrameworksMockingServicesTests:BroadcastQueueTest
Change-Id: I8c81893841026074ef78d88f9ecc82518164e50e
This commit is contained in:
Jeff Sharkey
2022-09-21 12:09:07 -06:00
parent 125e36870b
commit c6dde4f882
11 changed files with 394 additions and 175 deletions

View File

@@ -57,6 +57,8 @@ import static com.android.server.alarm.AlarmManagerService.RemovedAlarm.REMOVE_R
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.app.Activity;
import android.app.ActivityManagerInternal;
@@ -289,6 +291,7 @@ public class AlarmManagerService extends SystemService {
final DeliveryTracker mDeliveryTracker = new DeliveryTracker();
IBinder.DeathRecipient mListenerDeathRecipient;
Intent mTimeTickIntent;
Bundle mTimeTickOptions;
IAlarmListener mTimeTickTrigger;
PendingIntent mDateChangeSender;
boolean mInteractive = true;
@@ -1909,7 +1912,9 @@ public class AlarmManagerService extends SystemService {
Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_FOREGROUND
| Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
mTimeTickOptions = BroadcastOptions
.makeRemovingMatchingFilter(new IntentFilter(Intent.ACTION_TIME_TICK))
.toBundle();
mTimeTickTrigger = new IAlarmListener.Stub() {
@Override
public void doAlarm(final IAlarmCompleteListener callback) throws RemoteException {
@@ -1921,8 +1926,8 @@ public class AlarmManagerService extends SystemService {
// takes care of this automatically, but we're using the direct internal
// interface here rather than that client-side wrapper infrastructure.
mHandler.post(() -> {
getContext().sendBroadcastAsUser(mTimeTickIntent, UserHandle.ALL);
getContext().sendBroadcastAsUser(mTimeTickIntent, UserHandle.ALL, null,
mTimeTickOptions);
try {
callback.alarmComplete(this);
} catch (RemoteException e) { /* local method call */ }

View File

@@ -4667,7 +4667,7 @@ public class ActivityManager {
* @hide
*/
public static void broadcastStickyIntent(Intent intent, int userId) {
broadcastStickyIntent(intent, AppOpsManager.OP_NONE, userId);
broadcastStickyIntent(intent, AppOpsManager.OP_NONE, null, userId);
}
/**
@@ -4676,11 +4676,20 @@ public class ActivityManager {
* @hide
*/
public static void broadcastStickyIntent(Intent intent, int appOp, int userId) {
broadcastStickyIntent(intent, appOp, null, userId);
}
/**
* Convenience for sending a sticky broadcast. For internal use only.
*
* @hide
*/
public static void broadcastStickyIntent(Intent intent, int appOp, Bundle options, int userId) {
try {
getService().broadcastIntentWithFeature(
null, null, intent, null, null, Activity.RESULT_OK, null, null,
null /*requiredPermissions*/, null /*excludedPermissions*/,
null /*excludedPackages*/, appOp, null, false, true, userId);
null /*excludedPackages*/, appOp, options, false, true, userId);
} catch (RemoteException ex) {
}
}

View File

@@ -27,12 +27,15 @@ import android.compat.annotation.ChangeId;
import android.compat.annotation.Disabled;
import android.compat.annotation.EnabledSince;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Bundle;
import android.os.PowerExemptionManager;
import android.os.PowerExemptionManager.ReasonCode;
import android.os.PowerExemptionManager.TempAllowListType;
import java.util.Objects;
/**
* Helper class for building an options Bundle that can be used with
* {@link android.content.Context#sendBroadcast(android.content.Intent)
@@ -55,6 +58,7 @@ public class BroadcastOptions extends ComponentOptions {
private boolean mRequireCompatChangeEnabled = true;
private boolean mIsAlarmBroadcast = false;
private long mIdForResponseEvent;
private @Nullable IntentFilter mRemoveMatchingFilter;
/**
* Change ID which is invalid.
@@ -180,11 +184,25 @@ public class BroadcastOptions extends ComponentOptions {
private static final String KEY_ID_FOR_RESPONSE_EVENT =
"android:broadcast.idForResponseEvent";
/**
* Corresponds to {@link #setRemoveMatchingFilter}.
*/
private static final String KEY_REMOVE_MATCHING_FILTER =
"android:broadcast.removeMatchingFilter";
public static BroadcastOptions makeBasic() {
BroadcastOptions opts = new BroadcastOptions();
return opts;
}
/** {@hide} */
public static @NonNull BroadcastOptions makeRemovingMatchingFilter(
@NonNull IntentFilter removeMatchingFilter) {
BroadcastOptions opts = new BroadcastOptions();
opts.setRemoveMatchingFilter(removeMatchingFilter);
return opts;
}
private BroadcastOptions() {
super();
resetTemporaryAppAllowlist();
@@ -216,6 +234,8 @@ public class BroadcastOptions extends ComponentOptions {
mRequireCompatChangeEnabled = opts.getBoolean(KEY_REQUIRE_COMPAT_CHANGE_ENABLED, true);
mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT);
mIsAlarmBroadcast = opts.getBoolean(KEY_ALARM_BROADCAST, false);
mRemoveMatchingFilter = opts.getParcelable(KEY_REMOVE_MATCHING_FILTER,
IntentFilter.class);
}
/**
@@ -595,6 +615,29 @@ public class BroadcastOptions extends ComponentOptions {
return mIdForResponseEvent;
}
/**
* When enqueuing this broadcast, remove all pending broadcasts previously
* sent by this app which match the given filter.
* <p>
* For example, sending {@link Intent#ACTION_SCREEN_ON} would typically want
* to remove any pending {@link Intent#ACTION_SCREEN_OFF} broadcasts.
*
* @hide
*/
public void setRemoveMatchingFilter(@NonNull IntentFilter removeMatchingFilter) {
mRemoveMatchingFilter = Objects.requireNonNull(removeMatchingFilter);
}
/** @hide */
public void clearRemoveMatchingFilter() {
mRemoveMatchingFilter = null;
}
/** @hide */
public @Nullable IntentFilter getRemoveMatchingFilter() {
return mRemoveMatchingFilter;
}
/**
* Returns the created options as a Bundle, which can be passed to
* {@link android.content.Context#sendBroadcast(android.content.Intent)
@@ -640,6 +683,9 @@ public class BroadcastOptions extends ComponentOptions {
if (mIdForResponseEvent != 0) {
b.putLong(KEY_ID_FOR_RESPONSE_EVENT, mIdForResponseEvent);
}
if (mRemoveMatchingFilter != null) {
b.putParcelable(KEY_REMOVE_MATCHING_FILTER, mRemoveMatchingFilter);
}
return b.isEmpty() ? null : b;
}
}

View File

@@ -22,9 +22,12 @@ import static com.android.server.health.Utils.copyV1Battery;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.ContentObserver;
import android.hardware.health.HealthInfo;
import android.hardware.health.V2_1.BatteryCapacityLevel;
@@ -185,6 +188,17 @@ public final class BatteryService extends SystemService {
private ArrayDeque<Bundle> mBatteryLevelsEventQueue;
private long mLastBatteryLevelChangedSentMs;
private Bundle mBatteryChangedOptions = BroadcastOptions.makeRemovingMatchingFilter(
new IntentFilter(Intent.ACTION_BATTERY_CHANGED)).toBundle();
private Bundle mPowerConnectedOptions = BroadcastOptions.makeRemovingMatchingFilter(
new IntentFilter(Intent.ACTION_POWER_DISCONNECTED)).toBundle();
private Bundle mPowerDisconnectedOptions = BroadcastOptions.makeRemovingMatchingFilter(
new IntentFilter(Intent.ACTION_POWER_CONNECTED)).toBundle();
private Bundle mBatteryLowOptions = BroadcastOptions.makeRemovingMatchingFilter(
new IntentFilter(Intent.ACTION_BATTERY_OKAY)).toBundle();
private Bundle mBatteryOkayOptions = BroadcastOptions.makeRemovingMatchingFilter(
new IntentFilter(Intent.ACTION_BATTERY_LOW)).toBundle();
private MetricsLogger mMetricsLogger;
public BatteryService(Context context) {
@@ -606,7 +620,8 @@ public final class BatteryService extends SystemService {
mHandler.post(new Runnable() {
@Override
public void run() {
mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
mPowerConnectedOptions);
}
});
}
@@ -617,7 +632,8 @@ public final class BatteryService extends SystemService {
mHandler.post(new Runnable() {
@Override
public void run() {
mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
mPowerDisconnectedOptions);
}
});
}
@@ -630,7 +646,8 @@ public final class BatteryService extends SystemService {
mHandler.post(new Runnable() {
@Override
public void run() {
mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
mBatteryLowOptions);
}
});
} else if (mSentLowBatteryBroadcast &&
@@ -642,7 +659,8 @@ public final class BatteryService extends SystemService {
mHandler.post(new Runnable() {
@Override
public void run() {
mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
mBatteryOkayOptions);
}
});
}
@@ -712,7 +730,8 @@ public final class BatteryService extends SystemService {
+ ", info:" + mHealthInfo.toString());
}
mHandler.post(() -> ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL));
mHandler.post(() -> ActivityManager.broadcastStickyIntent(intent, AppOpsManager.OP_NONE,
mBatteryChangedOptions, UserHandle.USER_ALL));
}
private void sendBatteryLevelChangedIntentLocked() {

View File

@@ -17354,6 +17354,8 @@ public class ActivityManagerService extends IActivityManager.Stub
bOptions.setTemporaryAppAllowlist(mInternal.getBootTimeTempAllowListDuration(),
TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
PowerExemptionManager.REASON_LOCALE_CHANGED, "");
bOptions.setRemoveMatchingFilter(
new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null,
null, null, OP_NONE, bOptions.toBundle(), false, false, MY_PID,
SYSTEM_UID, Binder.getCallingUid(), Binder.getCallingPid(),

View File

@@ -18,7 +18,6 @@ package com.android.server.am;
import static com.android.server.am.BroadcastQueue.checkState;
import android.annotation.DurationMillisLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UptimeMillisLong;
@@ -28,12 +27,10 @@ import android.util.IndentingPrintWriter;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import com.android.server.am.BroadcastRecord.DeliveryState;
import java.util.ArrayDeque;
import java.util.Iterator;
import java.util.Objects;
import java.util.function.BiPredicate;
/**
* Queue of pending {@link BroadcastRecord} entries intended for delivery to a
@@ -46,7 +43,11 @@ import java.util.function.BiPredicate;
* Internally each queue consists of a pending broadcasts which are waiting to
* be dispatched, and a single active broadcast which is currently being
* dispatched.
* <p>
* This entire class is marked as {@code NotThreadSafe} since it's the
* responsibility of the caller to always interact with a relevant lock held.
*/
// @NotThreadSafe
class BroadcastProcessQueue {
final @NonNull BroadcastConstants constants;
final @NonNull String processName;
@@ -155,24 +156,42 @@ class BroadcastProcessQueue {
}
/**
* Skip any broadcasts matching the given predicate, marking them as
* {@link BroadcastRecord#DELIVERY_SKIPPED}. Typically used when a package
* or components have been disabled.
* <p>
* Note that we carefully preserve the broadcast in our queue to ensure that
* we follow our normal flow for "finishing" a broadcast, which is where we
* handle things like ordered broadcasts.
* Functional interface that tests a {@link BroadcastRecord} that has been
* previously enqueued in {@link BroadcastProcessQueue}.
*/
public boolean skipMatchingBroadcasts(@NonNull BiPredicate<BroadcastRecord, Object> predicate) {
@FunctionalInterface
public interface BroadcastPredicate {
public boolean test(@NonNull BroadcastRecord r, int index);
}
/**
* Functional interface that consumes a {@link BroadcastRecord} that has
* been previously enqueued in {@link BroadcastProcessQueue}.
*/
@FunctionalInterface
public interface BroadcastConsumer {
public void accept(@NonNull BroadcastRecord r, int index);
}
/**
* Remove any broadcasts matching the given predicate.
* <p>
* Predicates that choose to remove a broadcast <em>must</em> finish
* delivery of the matched broadcast, to ensure that situations like ordered
* broadcasts are handled consistently.
*/
public boolean removeMatchingBroadcasts(@NonNull BroadcastPredicate predicate,
@NonNull BroadcastConsumer consumer) {
boolean didSomething = false;
final Iterator<SomeArgs> it = mPending.iterator();
while (it.hasNext()) {
final SomeArgs args = it.next();
final BroadcastRecord record = (BroadcastRecord) args.arg1;
final int index = args.argi1;
final Object receiver = record.receivers.get(index);
if (predicate.test(record, receiver)) {
record.setDeliveryState(index, BroadcastRecord.DELIVERY_SKIPPED);
if (predicate.test(record, index)) {
consumer.accept(record, index);
args.recycle();
it.remove();
didSomething = true;
}
}
@@ -263,78 +282,48 @@ class BroadcastProcessQueue {
mActiveViaColdStart = false;
}
public void traceStartingBegin() {
public void traceProcessStartingBegin() {
Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
traceTrackName, toShortString() + " starting", hashCode());
}
public void traceRunningBegin() {
public void traceProcessRunningBegin() {
Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
traceTrackName, toShortString() + " running", hashCode());
}
public void traceEnd() {
public void traceProcessEnd() {
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
traceTrackName, hashCode());
}
public @DeliveryState int getActiveDeliveryState() {
checkState(isActive(), "isActive");
return mActive.delivery[mActiveIndex];
}
public void setActiveDeliveryState(@DeliveryState int deliveryState) {
checkState(isActive(), "isActive");
// Emit tracing events for the broadcast we're dispatching; the cookie
// here is unique within the track
public void traceActiveBegin() {
final int cookie = mActive.receivers.get(mActiveIndex).hashCode();
switch (deliveryState) {
case BroadcastRecord.DELIVERY_SCHEDULED:
Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
traceTrackName, mActive.toShortString() + " scheduled", cookie);
break;
case BroadcastRecord.DELIVERY_DELIVERED:
case BroadcastRecord.DELIVERY_SKIPPED:
case BroadcastRecord.DELIVERY_TIMEOUT:
case BroadcastRecord.DELIVERY_FAILURE:
// Only end trace events previously started by us
if (getActiveDeliveryState() == BroadcastRecord.DELIVERY_SCHEDULED) {
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
traceTrackName, cookie);
}
break;
}
Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
traceTrackName, mActive.toShortString() + " scheduled", cookie);
}
mActive.setDeliveryState(mActiveIndex, deliveryState);
public void traceActiveEnd() {
final int cookie = mActive.receivers.get(mActiveIndex).hashCode();
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
traceTrackName, cookie);
}
/**
* Return the delay between the currently active broadcast being enqueued
* and being scheduled to run for this process.
* Return the broadcast being actively dispatched in this process.
*/
public @DurationMillisLong long getActiveScheduledDelay() {
checkState(isActive(), "isActive");
return mActive.scheduledTime[mActiveIndex] - mActive.enqueueTime;
}
/**
* Return the delay between the currently active broadcast being scheduled
* to run and reaching a terminal state for this process.
*/
public @DurationMillisLong long getActiveDeliveredDelay() {
checkState(isActive(), "isActive");
return mActive.duration[mActiveIndex];
}
public @NonNull BroadcastRecord getActive() {
checkState(isActive(), "isActive");
return mActive;
}
public @NonNull Object getActiveReceiver() {
/**
* Return the index into {@link BroadcastRecord#receivers} of the receiver
* being actively dispatched in this process.
*/
public int getActiveIndex() {
checkState(isActive(), "isActive");
return mActive.receivers.get(mActiveIndex);
return mActiveIndex;
}
public boolean isEmpty() {

View File

@@ -21,14 +21,18 @@ import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_UNKNOWN;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
import static com.android.server.am.BroadcastProcessQueue.insertIntoRunnableList;
import static com.android.server.am.BroadcastProcessQueue.removeFromRunnableList;
import static com.android.server.am.BroadcastRecord.deliveryStateToString;
import static com.android.server.am.BroadcastRecord.getReceiverPackageName;
import static com.android.server.am.BroadcastRecord.getReceiverProcessName;
import static com.android.server.am.BroadcastRecord.getReceiverUid;
import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal;
import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER;
import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
@@ -42,6 +46,8 @@ import android.app.usage.UsageEvents.Event;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -64,6 +70,8 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import com.android.internal.os.TimeoutRecord;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.am.BroadcastProcessQueue.BroadcastConsumer;
import com.android.server.am.BroadcastProcessQueue.BroadcastPredicate;
import com.android.server.am.BroadcastRecord.DeliveryState;
import java.io.FileDescriptor;
@@ -73,7 +81,6 @@ import java.util.ArrayList;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
/**
@@ -345,15 +352,15 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
+ " from runnable to running; process is " + queue.app);
// Allocate this available permit and start running!
final int index = getRunningIndexOf(null);
mRunning[index] = queue;
final int queueIndex = getRunningIndexOf(null);
mRunning[queueIndex] = queue;
avail--;
// Remove ourselves from linked list of runnable things
mRunnableHead = removeFromRunnableList(mRunnableHead, queue);
// Emit all trace events for this process into a consistent track
queue.traceTrackName = TAG + ".mRunning[" + index + "]";
queue.traceTrackName = TAG + ".mRunning[" + queueIndex + "]";
// If we're already warm, boost OOM adjust now; if cold we'll boost
// it after the app has been started
@@ -365,10 +372,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// otherwise we'll wait for the cold start to circle back around
queue.makeActiveNextPending();
if (processWarm) {
queue.traceRunningBegin();
queue.traceProcessRunningBegin();
scheduleReceiverWarmLocked(queue);
} else {
queue.traceStartingBegin();
queue.traceProcessStartingBegin();
scheduleReceiverColdLocked(queue);
}
@@ -401,8 +408,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
final BroadcastProcessQueue queue = mRunningColdStart;
mRunningColdStart = null;
queue.traceEnd();
queue.traceRunningBegin();
queue.traceProcessEnd();
queue.traceProcessRunningBegin();
scheduleReceiverWarmLocked(queue);
// We might be willing to kick off another cold start
@@ -468,6 +475,17 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// TODO: handle empty receivers to deliver result immediately
if (r.receivers == null) return;
final IntentFilter removeMatchingFilter = (r.options != null)
? r.options.getRemoveMatchingFilter() : null;
if (removeMatchingFilter != null) {
final Predicate<Intent> removeMatching = removeMatchingFilter.asPredicate();
skipMatchingBroadcasts(QUEUE_PREDICATE_ANY, (testRecord, testReceiver) -> {
// We only allow caller to clear broadcasts they enqueued
return (testRecord.callingUid == r.callingUid)
&& removeMatching.test(testRecord.intent);
});
}
r.enqueueTime = SystemClock.uptimeMillis();
r.enqueueRealTime = SystemClock.elapsedRealtime();
r.enqueueClockTime = System.currentTimeMillis();
@@ -494,7 +512,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
queue.setActiveViaColdStart(true);
final BroadcastRecord r = queue.getActive();
final Object receiver = queue.getActiveReceiver();
final int index = queue.getActiveIndex();
final Object receiver = r.receivers.get(index);
final ApplicationInfo info = ((ResolveInfo) receiver).activityInfo.applicationInfo;
final ComponentName component = ((ResolveInfo) receiver).activityInfo.getComponentName();
@@ -534,15 +553,17 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
final ProcessRecord app = queue.app;
final BroadcastRecord r = queue.getActive();
final Object receiver = queue.getActiveReceiver();
final int index = queue.getActiveIndex();
final Object receiver = r.receivers.get(index);
// If someone already skipped us, finish immediately; typically due to a
// component being disabled
if (queue.getActiveDeliveryState() == BroadcastRecord.DELIVERY_SKIPPED) {
finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
// If someone already finished this broadcast, finish immediately
final int oldDeliveryState = getDeliveryState(r, index);
if (isDeliveryStateTerminal(oldDeliveryState)) {
finishReceiverLocked(queue, oldDeliveryState);
return;
}
// Consider additional cases where we'd want fo finish immediately
if (app.isInFullBackup()) {
finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
return;
@@ -583,13 +604,13 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}
if (DEBUG_BROADCAST) logv("Scheduling " + r + " to warm " + app);
queue.setActiveDeliveryState(BroadcastRecord.DELIVERY_SCHEDULED);
setDeliveryState(queue, r, index, receiver, BroadcastRecord.DELIVERY_SCHEDULED);
final IApplicationThread thread = app.getThread();
if (thread != null) {
try {
if (receiver instanceof BroadcastFilter) {
notifyScheduleRegisteredReceiver(app, r);
notifyScheduleRegisteredReceiver(app, r, (BroadcastFilter) receiver);
thread.scheduleRegisteredReceiver(
((BroadcastFilter) receiver).receiverList.receiver, receiverIntent,
r.resultCode, r.resultData, r.resultExtras, r.ordered, r.initialSticky,
@@ -601,7 +622,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED);
}
} else {
notifyScheduleReceiver(app, r, receiverIntent);
notifyScheduleReceiver(app, r, (ResolveInfo) receiver);
thread.scheduleReceiver(receiverIntent, ((ResolveInfo) receiver).activityInfo,
null, r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
app.mState.getReportedProcState());
@@ -653,7 +674,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// receivers as skipped
if (r.ordered && r.resultAbort) {
for (int i = r.finishedCount + 1; i < r.receivers.size(); i++) {
r.setDeliveryState(i, BroadcastRecord.DELIVERY_SKIPPED);
setDeliveryState(null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED);
}
}
@@ -666,44 +687,20 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
final ProcessRecord app = queue.app;
final BroadcastRecord r = queue.getActive();
final int index = queue.getActiveIndex();
final Object receiver = r.receivers.get(index);
queue.setActiveDeliveryState(deliveryState);
if (deliveryState != BroadcastRecord.DELIVERY_DELIVERED) {
Slog.w(TAG, "Delivery state of " + queue.getActive() + " to " + queue + " changed to "
+ BroadcastRecord.deliveryStateToString(deliveryState));
}
setDeliveryState(queue, r, index, receiver, deliveryState);
if (deliveryState == BroadcastRecord.DELIVERY_TIMEOUT) {
if (app != null && !app.isDebugging()) {
mService.appNotResponding(queue.app, TimeoutRecord
.forBroadcastReceiver("Broadcast of " + queue.getActive().toShortString()));
.forBroadcastReceiver("Broadcast of " + r.toShortString()));
}
} else {
mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT, queue);
}
// We carefully only increment this here once we've confidently finished
// a given receiver; everyone must come through this path to ensure all
// bookkeeping is handled consistently
r.finishedCount++;
notifyFinishReceiver(queue);
if (r.ordered) {
if (r.finishedCount < r.receivers.size()) {
// We just finished an ordered receiver, which means the next
// receiver might now be runnable
final Object nextReceiver = r.receivers.get(r.finishedCount);
final BroadcastProcessQueue nextQueue = getProcessQueue(
getReceiverProcessName(nextReceiver), getReceiverUid(nextReceiver));
nextQueue.invalidateRunnableAt();
updateRunnableList(nextQueue);
} else {
// Everything finished, so deliver final result
scheduleResultTo(r);
}
}
// 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)
@@ -717,10 +714,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
} else {
// We've drained running broadcasts; maybe move back to runnable
queue.makeActiveIdle();
queue.traceEnd();
queue.traceProcessEnd();
final int index = getRunningIndexOf(queue);
mRunning[index] = null;
final int queueIndex = getRunningIndexOf(queue);
mRunning[queueIndex] = null;
updateRunnableList(queue);
enqueueUpdateRunningList();
@@ -731,17 +728,67 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}
}
private static final Predicate<BroadcastProcessQueue> QUEUE_PREDICATE_ANY =
(q) -> true;
private static final BiPredicate<BroadcastRecord, Object> BROADCAST_PREDICATE_ANY =
(r, o) -> true;
/**
* Set the delivery state on the given broadcast, then apply any additional
* bookkeeping related to ordered broadcasts.
*/
private void setDeliveryState(@Nullable BroadcastProcessQueue queue,
@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 "
+ deliveryStateToString(oldDeliveryState) + " to "
+ deliveryStateToString(newDeliveryState));
}
r.setDeliveryState(index, newDeliveryState);
// Emit any relevant tracing results when we're changing the delivery
// state as part of running from a queue
if (queue != null) {
if (newDeliveryState == BroadcastRecord.DELIVERY_SCHEDULED) {
queue.traceActiveBegin();
} else if ((oldDeliveryState == BroadcastRecord.DELIVERY_SCHEDULED)
&& isDeliveryStateTerminal(newDeliveryState)) {
queue.traceActiveEnd();
}
}
// If we're moving into a terminal state, we might have internal
// bookkeeping to update for ordered broadcasts
if (!isDeliveryStateTerminal(oldDeliveryState)
&& isDeliveryStateTerminal(newDeliveryState)) {
r.finishedCount++;
notifyFinishReceiver(queue, r, index, receiver);
if (r.ordered) {
if (r.finishedCount < r.receivers.size()) {
// We just finished an ordered receiver, which means the
// next receiver might now be runnable
final Object nextReceiver = r.receivers.get(r.finishedCount);
final BroadcastProcessQueue nextQueue = getProcessQueue(
getReceiverProcessName(nextReceiver), getReceiverUid(nextReceiver));
nextQueue.invalidateRunnableAt();
updateRunnableList(nextQueue);
} else {
// Everything finished, so deliver final result
scheduleResultTo(r);
}
}
}
}
private @DeliveryState int getDeliveryState(@NonNull BroadcastRecord r, int index) {
return r.getDeliveryState(index);
}
@Override
public boolean cleanupDisabledPackageReceiversLocked(@Nullable String packageName,
@Nullable Set<String> filterByClasses, int userId) {
final Predicate<BroadcastProcessQueue> queuePredicate;
final BiPredicate<BroadcastRecord, Object> broadcastPredicate;
// TODO: apply packageName filter inside broadcastPredicate
final BroadcastPredicate broadcastPredicate;
if (packageName != null) {
// Caller provided a package and user ID, so we're focused on queues
// belonging to a specific UID
@@ -754,15 +801,21 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// If caller provided a set of classes, filter to skip only those;
// otherwise we skip all broadcasts
if (filterByClasses != null) {
broadcastPredicate = (r, o) -> {
if (o instanceof ResolveInfo) {
return filterByClasses.contains(((ResolveInfo) o).activityInfo.name);
broadcastPredicate = (r, i) -> {
final Object receiver = r.receivers.get(i);
if (receiver instanceof ResolveInfo) {
final ActivityInfo info = ((ResolveInfo) receiver).activityInfo;
return packageName.equals(info.packageName)
&& filterByClasses.contains(info.name);
} else {
return false;
}
};
} else {
broadcastPredicate = BROADCAST_PREDICATE_ANY;
broadcastPredicate = (r, i) -> {
final Object receiver = r.receivers.get(i);
return packageName.equals(getReceiverPackageName(receiver));
};
}
} else {
// Caller is cleaning up an entire user ID; skip all broadcasts
@@ -771,7 +824,25 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
};
broadcastPredicate = BROADCAST_PREDICATE_ANY;
}
return skipMatchingBroadcasts(queuePredicate, broadcastPredicate);
}
private static final Predicate<BroadcastProcessQueue> QUEUE_PREDICATE_ANY =
(q) -> true;
private static final BroadcastPredicate BROADCAST_PREDICATE_ANY =
(r, i) -> true;
/**
* Typical consumer that will skip the given broadcast, usually as a result
* of it matching a predicate.
*/
private final BroadcastConsumer mBroadcastConsumerSkip = (r, i) -> {
setDeliveryState(null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED);
};
private boolean skipMatchingBroadcasts(
@NonNull Predicate<BroadcastProcessQueue> queuePredicate,
@NonNull BroadcastPredicate broadcastPredicate) {
// Note that we carefully preserve any "skipped" broadcasts in their
// queues so that we follow our normal flow for "finishing" a broadcast,
// which is where we handle things like ordered broadcasts.
@@ -780,7 +851,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
while (leaf != null) {
if (queuePredicate.test(leaf)) {
didSomething |= leaf.skipMatchingBroadcasts(broadcastPredicate);
didSomething |= leaf.removeMatchingBroadcasts(broadcastPredicate,
mBroadcastConsumerSkip);
}
leaf = leaf.processNameNext;
}
@@ -909,7 +981,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
* a registered receiver, typically for internal bookkeeping.
*/
private void notifyScheduleRegisteredReceiver(@NonNull ProcessRecord app,
@NonNull BroadcastRecord r) {
@NonNull BroadcastRecord r, @NonNull BroadcastFilter receiver) {
reportUsageStatsBroadcastDispatched(app, r);
}
@@ -918,22 +990,25 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
* a manifest receiver, typically for internal bookkeeping.
*/
private void notifyScheduleReceiver(@NonNull ProcessRecord app,
@NonNull BroadcastRecord r, @NonNull Intent receiverIntent) {
@NonNull BroadcastRecord r, @NonNull ResolveInfo receiver) {
reportUsageStatsBroadcastDispatched(app, r);
final String receiverPackageName = receiver.activityInfo.packageName;
app.addPackage(receiverPackageName,
receiver.activityInfo.applicationInfo.longVersionCode, mService.mProcessStats);
final boolean targetedBroadcast = r.intent.getComponent() != null;
final boolean targetedSelf = Objects.equals(r.callerPackage,
receiverIntent.getComponent().getPackageName());
final boolean targetedSelf = Objects.equals(r.callerPackage, receiverPackageName);
if (targetedBroadcast && !targetedSelf) {
mService.mUsageStatsService.reportEvent(receiverIntent.getComponent().getPackageName(),
mService.mUsageStatsService.reportEvent(receiverPackageName,
r.userId, Event.APP_COMPONENT_USED);
}
mService.notifyPackageUse(receiverIntent.getComponent().getPackageName(),
mService.notifyPackageUse(receiverPackageName,
PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
mService.mPackageManagerInt.setPackageStoppedState(
receiverIntent.getComponent().getPackageName(), false, r.userId);
receiverPackageName, false, r.userId);
}
private void reportUsageStatsBroadcastDispatched(@NonNull ProcessRecord app,
@@ -961,26 +1036,30 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
* Inform other parts of OS that the given broadcast was just finished,
* typically for internal bookkeeping.
*/
private void notifyFinishReceiver(@NonNull BroadcastProcessQueue queue) {
final BroadcastRecord r = queue.getActive();
private void notifyFinishReceiver(@Nullable BroadcastProcessQueue queue,
@NonNull BroadcastRecord r, int index, @NonNull Object receiver) {
// Report statistics for each individual receiver
final int uid = queue.uid;
final int uid = getReceiverUid(receiver);
final int senderUid = (r.callingUid == -1) ? Process.SYSTEM_UID : r.callingUid;
final String actionName = ActivityManagerService.getShortAction(r.intent.getAction());
final int receiverType = (queue.getActiveReceiver() instanceof BroadcastFilter)
final int receiverType = (receiver instanceof BroadcastFilter)
? BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME
: BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST;
final int procStartType = queue.getActiveViaColdStart()
? BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD
: BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
final int type;
if (queue == null) {
type = BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_UNKNOWN;
} else if (queue.getActiveViaColdStart()) {
type = BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
} else {
type = BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
}
// With the new per-process queues, there's no delay between being
// "dispatched" and "scheduled", so we report no "receive delay"
final long dispatchDelay = queue.getActiveScheduledDelay();
final long dispatchDelay = r.scheduledTime[index] - r.enqueueTime;
final long receiveDelay = 0;
final long finishDelay = queue.getActiveDeliveredDelay();
final long finishDelay = r.duration[index];
FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, uid, senderUid, actionName,
receiverType, procStartType, dispatchDelay, receiveDelay, finishDelay);
receiverType, type, dispatchDelay, receiveDelay, finishDelay);
final boolean recordFinished = (r.finishedCount == r.receivers.size());
if (recordFinished) {

View File

@@ -23,7 +23,9 @@ import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROA
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_CHANGE_ID;
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_TARGET_T_ONLY;
import static com.android.server.am.BroadcastQueue.checkState;
import android.annotation.DurationMillisLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -168,6 +170,22 @@ final class BroadcastRecord extends Binder {
}
}
/**
* Return if the given delivery state is "terminal", where no additional
* delivery state changes will be made.
*/
static boolean isDeliveryStateTerminal(@DeliveryState int deliveryState) {
switch (deliveryState) {
case DELIVERY_DELIVERED:
case DELIVERY_SKIPPED:
case DELIVERY_TIMEOUT:
case DELIVERY_FAILURE:
return true;
default:
return false;
}
}
ProcessRecord curApp; // hosting application of current receiver.
ComponentName curComponent; // the receiver class that is currently running.
ActivityInfo curReceiver; // the manifest receiver that is currently running.
@@ -546,6 +564,10 @@ final class BroadcastRecord extends Binder {
}
}
@DeliveryState int getDeliveryState(int index) {
return delivery[index];
}
boolean isForeground() {
return (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0;
}
@@ -611,7 +633,7 @@ final class BroadcastRecord extends Binder {
}
}
static String getReceiverProcessName(@NonNull Object receiver) {
static @NonNull String getReceiverProcessName(@NonNull Object receiver) {
if (receiver instanceof BroadcastFilter) {
return ((BroadcastFilter) receiver).receiverList.app.processName;
} else /* if (receiver instanceof ResolveInfo) */ {
@@ -619,6 +641,14 @@ final class BroadcastRecord extends Binder {
}
}
static @NonNull String getReceiverPackageName(@NonNull Object receiver) {
if (receiver instanceof BroadcastFilter) {
return ((BroadcastFilter) receiver).receiverList.app.info.packageName;
} else /* if (receiver instanceof ResolveInfo) */ {
return ((ResolveInfo) receiver).activityInfo.packageName;
}
}
public BroadcastRecord maybeStripForHistory() {
if (!intent.canStripForHistory()) {
return this;

View File

@@ -20,10 +20,12 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.InputManagerInternal;
import android.media.AudioManager;
@@ -32,6 +34,7 @@ import android.media.RingtoneManager;
import android.metrics.LogMaker;
import android.net.Uri;
import android.os.BatteryStats;
import android.os.Bundle;
import android.os.Handler;
import android.os.IWakeLockCallback;
import android.os.Looper;
@@ -137,7 +140,9 @@ public class Notifier {
private final NotifierHandler mHandler;
private final Executor mBackgroundExecutor;
private final Intent mScreenOnIntent;
private final Bundle mScreenOnOptions;
private final Intent mScreenOffIntent;
private final Bundle mScreenOffOptions;
// True if the device should suspend when the screen is off due to proximity.
private final boolean mSuspendWhenScreenOffDueToProximityConfig;
@@ -199,10 +204,14 @@ public class Notifier {
mScreenOnIntent.addFlags(
Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND
| Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
mScreenOnOptions = BroadcastOptions.makeRemovingMatchingFilter(
new IntentFilter(Intent.ACTION_SCREEN_OFF)).toBundle();
mScreenOffIntent = new Intent(Intent.ACTION_SCREEN_OFF);
mScreenOffIntent.addFlags(
Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND
| Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
mScreenOffOptions = BroadcastOptions.makeRemovingMatchingFilter(
new IntentFilter(Intent.ACTION_SCREEN_ON)).toBundle();
mSuspendWhenScreenOffDueToProximityConfig = context.getResources().getBoolean(
com.android.internal.R.bool.config_suspendWhenScreenOffDueToProximity);
@@ -788,7 +797,8 @@ public class Notifier {
if (mActivityManagerInternal.isSystemReady()) {
mContext.sendOrderedBroadcastAsUser(mScreenOnIntent, UserHandle.ALL, null,
mWakeUpBroadcastDone, mHandler, 0, null, null);
AppOpsManager.OP_NONE, mScreenOnOptions, mWakeUpBroadcastDone, mHandler,
0, null, null);
} else {
EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 2, 1);
sendNextBroadcast();
@@ -811,7 +821,8 @@ public class Notifier {
if (mActivityManagerInternal.isSystemReady()) {
mContext.sendOrderedBroadcastAsUser(mScreenOffIntent, UserHandle.ALL, null,
mGoToSleepBroadcastDone, mHandler, 0, null, null);
AppOpsManager.OP_NONE, mScreenOffOptions, mGoToSleepBroadcastDone, mHandler,
0, null, null);
} else {
EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 3, 1);
sendNextBroadcast();

View File

@@ -37,12 +37,14 @@ import android.app.Activity;
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.HandlerThread;
import android.os.UserHandle;
import android.provider.Settings;
import androidx.test.filters.SmallTest;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -88,6 +90,11 @@ public class BroadcastQueueModernImplTest {
doReturn(4L).when(mQueue4).getRunnableAt();
}
@After
public void tearDown() throws Exception {
mHandlerThread.quit();
}
private static void assertOrphan(BroadcastProcessQueue queue) {
assertNull(queue.runnableAtNext);
assertNull(queue.runnableAtPrev);
@@ -291,4 +298,35 @@ public class BroadcastQueueModernImplTest {
assertTrue(queue.isRunnable());
assertEquals(airplaneRecord.enqueueTime, queue.getRunnableAt());
}
/**
* Verify that sending a broadcast that removes any matching pending
* broadcasts is applied as expected.
*/
@Test
public void testRemoveMatchingFilter() {
final Intent screenOn = new Intent(Intent.ACTION_SCREEN_ON);
final BroadcastOptions optionsOn = BroadcastOptions.makeBasic();
optionsOn.setRemoveMatchingFilter(new IntentFilter(Intent.ACTION_SCREEN_OFF));
final Intent screenOff = new Intent(Intent.ACTION_SCREEN_OFF);
final BroadcastOptions optionsOff = BroadcastOptions.makeBasic();
optionsOff.setRemoveMatchingFilter(new IntentFilter(Intent.ACTION_SCREEN_ON));
// Halt all processing so that we get a consistent view
mHandlerThread.getLooper().getQueue().postSyncBarrier();
mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, optionsOn));
mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOff, optionsOff));
mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, optionsOn));
mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOff, optionsOff));
// Marching through the queue we should only have one SCREEN_OFF
// broadcast, since that's the last state we dispatched
final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN,
getUidForPackage(PACKAGE_GREEN));
queue.makeActiveNextPending();
assertEquals(Intent.ACTION_SCREEN_OFF, queue.getActive().intent.getAction());
assertTrue(queue.isEmpty());
}
}

View File

@@ -654,37 +654,28 @@ public class BroadcastQueueTest {
receiverApp.mState.getReportedProcState());
verify(mAms, times(2)).enqueueOomAdjTargetLocked(eq(receiverApp));
// Confirm that app was thawed
if ((mImpl == Impl.DEFAULT) && (receiverApp == receiverBlueApp)) {
// Nuance: the default implementation doesn't ask for manifest
// cold-started apps to be thawed, but the modern stack does
} else {
// Confirm that app was thawed
verify(mAms.mOomAdjuster.mCachedAppOptimizer).unfreezeTemporarily(eq(receiverApp),
eq(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER));
// Confirm that we added package to process
verify(receiverApp, atLeastOnce()).addPackage(eq(receiverApp.info.packageName),
anyLong(), any());
}
// Confirm that we've reported package as being used
verify(mAms, atLeastOnce()).notifyPackageUse(eq(receiverApp.info.packageName),
eq(PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER));
// Confirm that we unstopped manifest receivers
verify(mAms.mPackageManagerInt, atLeastOnce()).setPackageStoppedState(
eq(receiverApp.info.packageName), eq(false), eq(UserHandle.USER_SYSTEM));
}
// Confirm that we've reported relevant packages as being used, but
// only called for manifest receivers
verify(mAms, never()).notifyPackageUse(eq(PACKAGE_RED),
eq(PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER));
verify(mAms, atLeastOnce()).notifyPackageUse(eq(PACKAGE_GREEN),
eq(PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER));
verify(mAms, atLeastOnce()).notifyPackageUse(eq(PACKAGE_BLUE),
eq(PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER));
verify(mAms, atLeastOnce()).notifyPackageUse(eq(PACKAGE_YELLOW),
eq(PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER));
// Confirm that we unstopped manifest receivers
verify(mAms.mPackageManagerInt, never()).setPackageStoppedState(eq(PACKAGE_RED),
eq(false), eq(UserHandle.USER_SYSTEM));
verify(mAms.mPackageManagerInt, atLeastOnce()).setPackageStoppedState(eq(PACKAGE_GREEN),
eq(false), eq(UserHandle.USER_SYSTEM));
verify(mAms.mPackageManagerInt, atLeastOnce()).setPackageStoppedState(eq(PACKAGE_BLUE),
eq(false), eq(UserHandle.USER_SYSTEM));
verify(mAms.mPackageManagerInt, atLeastOnce()).setPackageStoppedState(eq(PACKAGE_YELLOW),
eq(false), eq(UserHandle.USER_SYSTEM));
// Confirm that we've reported expected usage events
verify(mAms.mUsageStatsService).reportBroadcastDispatched(eq(callerApp.uid),
eq(PACKAGE_YELLOW), eq(UserHandle.SYSTEM), eq(42L), anyLong(), anyInt());