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
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 5b61f9a..22f70ce 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -57,6 +57,8 @@
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 @@
final DeliveryTracker mDeliveryTracker = new DeliveryTracker();
IBinder.DeathRecipient mListenerDeathRecipient;
Intent mTimeTickIntent;
+ Bundle mTimeTickOptions;
IAlarmListener mTimeTickTrigger;
PendingIntent mDateChangeSender;
boolean mInteractive = true;
@@ -1909,7 +1912,9 @@
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 @@
// 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 */ }
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index dfef279..8ae16df6 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4667,7 +4667,7 @@
* @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 @@
* @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) {
}
}
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index aa5fa5b..c2df802 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -27,12 +27,15 @@
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 @@
private boolean mRequireCompatChangeEnabled = true;
private boolean mIsAlarmBroadcast = false;
private long mIdForResponseEvent;
+ private @Nullable IntentFilter mRemoveMatchingFilter;
/**
* Change ID which is invalid.
@@ -180,11 +184,25 @@
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 @@
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);
}
/**
@@ -596,6 +616,29 @@
}
/**
+ * 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)
* Context.sendBroadcast(Intent)} and related methods.
@@ -640,6 +683,9 @@
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;
}
}
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index b96d33c..4278b3e 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -22,9 +22,12 @@
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 @@
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 @@
mHandler.post(new Runnable() {
@Override
public void run() {
- mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
+ mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
+ mPowerConnectedOptions);
}
});
}
@@ -617,7 +632,8 @@
mHandler.post(new Runnable() {
@Override
public void run() {
- mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
+ mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
+ mPowerDisconnectedOptions);
}
});
}
@@ -630,7 +646,8 @@
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 @@
mHandler.post(new Runnable() {
@Override
public void run() {
- mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
+ mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
+ mBatteryOkayOptions);
}
});
}
@@ -712,7 +730,8 @@
+ ", 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() {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5ea3a01..ec30097 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -17354,6 +17354,8 @@
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(),
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 70fb7a3..77eefb4 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -18,7 +18,6 @@
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 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 @@
* 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 @@
}
/**
- * 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 @@
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() {
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index d60ad99..a36a9f6 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -21,14 +21,18 @@
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.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.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.Objects;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
-import java.util.function.BiPredicate;
import java.util.function.Predicate;
/**
@@ -345,15 +352,15 @@
+ " 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 @@
// 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 @@
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 @@
// 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 @@
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 @@
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 @@
}
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 @@
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 @@
// 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 @@
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 @@
} 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 @@
}
}
- 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 @@
// 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 @@
};
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 @@
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 @@
* 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 @@
* 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 @@
* 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) {
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 74f32e3..ae7f2a5 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -23,7 +23,9 @@
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 @@
}
}
+ /**
+ * 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 @@
}
}
+ @DeliveryState int getDeliveryState(int index) {
+ return delivery[index];
+ }
+
boolean isForeground() {
return (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0;
}
@@ -611,7 +633,7 @@
}
}
- 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 @@
}
}
+ 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;
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index dad9584..9336be5 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -20,10 +20,12 @@
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.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 @@
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 @@
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 @@
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 @@
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();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 2b1f4a3..e09b80e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -37,12 +37,14 @@
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 @@
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 @@
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());
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index adb62d0..cf5d113 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -654,37 +654,28 @@
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());