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 5b61f9abc357e..22f70ce8e0e65 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 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 */ } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index dfef279be7c53..8ae16df64ace3 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -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) { } } diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java index aa5fa5b19117a..c2df8022af0ff 100644 --- a/core/java/android/app/BroadcastOptions.java +++ b/core/java/android/app/BroadcastOptions.java @@ -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. + *

+ * 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; } } diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index b96d33c0f44b6..4278b3edc3ebf 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -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 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() { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 5ea3a0139a016..ec30097dea231 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 @@ 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(), diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index 70fb7a3e962b7..77eefb4e27433 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 @@ 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. + *

+ * 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. - *

- * 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 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. + *

+ * Predicates that choose to remove a broadcast must 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 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() { diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index d60ad991d91e6..a36a9f64bded8 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 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 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 QUEUE_PREDICATE_ANY = - (q) -> true; - private static final BiPredicate 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 filterByClasses, int userId) { final Predicate queuePredicate; - final BiPredicate 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 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 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) { diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java index 74f32e3f141d7..ae7f2a5d8565a 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_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; diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java index dad9584c67225..9336be5555e48 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.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(); 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 2b1f4a3ce56ca..e09b80e196b1a 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.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()); + } } 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 adb62d0da2f92..cf5d1133b741e 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 @@ 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());