From c7886e98a820264d7dae40ee8067bde7e7714102 Mon Sep 17 00:00:00 2001 From: Lee Shombert Date: Sat, 10 Dec 2022 12:11:01 -0800 Subject: [PATCH] BroadcastQueue now uses scheduleReceiverList Bug: 253906553 Convert the broadcast queues to the new scheduleReceiverList API in IApplicationThread. The changes are as follows: 1. The ReceiverBatch class is created to handle the arguments to scheduleReceiverList. The class has additional functionality in preparation for allowing batches of more than one receiver. The ReceiverBatch class is essentially bookkeeping: it maintains lists of information for a client. The ReceiverBatch class has a single() method that does everything necessary to dispatch a single manifest or registered receiver. In normal operation, the ReceiverBatch class uses object pools and does not do any allocation after it has been instantiated. There is a test mode that always allocates new objects. See the notes on BroadcastQueueTest. 2. BroadcastQueueImpl and BroadcastQueueModernImpl each contain an instance of ReceiverBatch, size 1, and use that instance to invoke scheduleReceiverList. 3. BroadcastQueueTest is modified to test against the new thread API. There are changes in the support methods in the class. The intent is that the actual test code looks like a textual rewrite rather than a logical rewrite. 4. BroadcastConstants includes a MAX_BROADCAST_BATCH_SIZE, which is set to 1. This is not required for this CL but is a stake in the ground to minimize future merge conflicts. This constant will be increased when the modern queue supports more than one receiver in a batch. 5. BroadcastRecord has two new per-receiver attributes: the transmit group and the transmit order. These are initialized in the ReceiverBatch.recordBatch() method (which is not called in this commit). Again, this is a stake in the ground to minimize future merge conflicts. There is no behavioral change in this commit. Test: atest * FrameworksMockingServicesTests:BroadcastQueueTest Change-Id: I543092cdd68a979fb466a2156c251c6c4f024b61 --- .../android/server/am/BroadcastConstants.java | 12 + .../android/server/am/BroadcastQueueImpl.java | 19 +- .../server/am/BroadcastQueueModernImpl.java | 31 +- .../server/am/BroadcastReceiverBatch.java | 346 ++++++++++++++++++ .../android/server/am/BroadcastRecord.java | 6 + .../android/server/am/BroadcastQueueTest.java | 342 ++++++++++++----- 6 files changed, 642 insertions(+), 114 deletions(-) create mode 100644 services/core/java/com/android/server/am/BroadcastReceiverBatch.java diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java index f5d1c106705dc..fe5888df238ed 100644 --- a/services/core/java/com/android/server/am/BroadcastConstants.java +++ b/services/core/java/com/android/server/am/BroadcastConstants.java @@ -236,6 +236,15 @@ public class BroadcastConstants { private static final int DEFAULT_MAX_HISTORY_SUMMARY_SIZE = ActivityManager.isLowRamDeviceStatic() ? 25 : 300; + /** + * For {@link BroadcastQueueModernImpl}: Maximum number of broadcast receivers to process in a + * single synchronized block. Up to this many messages may be dispatched in a single binder + * call. Set this to 1 (or zero) for pre-batch behavior. + */ + public int MAX_BROADCAST_BATCH_SIZE = DEFAULT_MAX_BROADCAST_BATCH_SIZE; + private static final String KEY_MAX_BROADCAST_BATCH_SIZE = "bcast_max_batch_size"; + private static final int DEFAULT_MAX_BROADCAST_BATCH_SIZE = 1; + // Settings override tracking for this instance private String mSettingsKey; private SettingsObserver mSettingsObserver; @@ -373,6 +382,8 @@ public class BroadcastConstants { DEFAULT_MAX_HISTORY_COMPLETE_SIZE); MAX_HISTORY_SUMMARY_SIZE = getDeviceConfigInt(KEY_MAX_HISTORY_SUMMARY_SIZE, DEFAULT_MAX_HISTORY_SUMMARY_SIZE); + MAX_BROADCAST_BATCH_SIZE = getDeviceConfigInt(KEY_MAX_BROADCAST_BATCH_SIZE, + DEFAULT_MAX_BROADCAST_BATCH_SIZE); } } @@ -418,6 +429,7 @@ public class BroadcastConstants { MAX_CONSECUTIVE_URGENT_DISPATCHES).println(); pw.print(KEY_MAX_CONSECUTIVE_NORMAL_DISPATCHES, MAX_CONSECUTIVE_NORMAL_DISPATCHES).println(); + pw.print(KEY_MAX_BROADCAST_BATCH_SIZE, MAX_BROADCAST_BATCH_SIZE).println(); pw.decreaseIndent(); pw.println(); } diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java index 1a72fef0683e2..8946ada1daf97 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java @@ -73,6 +73,7 @@ import android.util.Slog; import android.util.SparseIntArray; import android.util.proto.ProtoOutputStream; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.TimeoutRecord; import com.android.internal.util.FrameworkStatsLog; import com.android.server.LocalServices; @@ -186,6 +187,14 @@ public class BroadcastQueueImpl extends BroadcastQueue { } } + /** + * This single object allows the queue to dispatch receivers using scheduleReceiverList + * without constantly allocating new ReceiverInfo objects or ArrayLists. This queue + * implementation is known to have a maximum size of one entry. + */ + @VisibleForTesting + final BroadcastReceiverBatch mReceiverBatch = new BroadcastReceiverBatch(1); + BroadcastQueueImpl(ActivityManagerService service, Handler handler, String name, BroadcastConstants constants, boolean allowDelayBehindServices, int schedGroup) { @@ -388,10 +397,11 @@ public class BroadcastQueueImpl extends BroadcastQueue { + ": " + r); mService.notifyPackageUse(r.intent.getComponent().getPackageName(), PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER); - thread.scheduleReceiver(prepareReceiverIntent(r.intent, r.curFilteredExtras), + thread.scheduleReceiverList(mReceiverBatch.manifestReceiver( + prepareReceiverIntent(r.intent, r.curFilteredExtras), r.curReceiver, null /* compatInfo (unused but need to keep method signature) */, r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId, - app.mState.getReportedProcState()); + app.mState.getReportedProcState())); if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Process cur broadcast " + r + " DELIVERED for app " + app); started = true; @@ -725,9 +735,10 @@ public class BroadcastQueueImpl extends BroadcastQueue { // If we have an app thread, do the call through that so it is // correctly ordered with other one-way calls. try { - thread.scheduleRegisteredReceiver(receiver, intent, resultCode, + thread.scheduleReceiverList(mReceiverBatch.registeredReceiver( + receiver, intent, resultCode, data, extras, ordered, sticky, sendingUser, - app.mState.getReportedProcState()); + app.mState.getReportedProcState())); } catch (RemoteException ex) { // Failed to call into the process. It's either dying or wedged. Kill it gently. synchronized (mService) { diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index eb5c03bec5606..baa906fc3db0c 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -201,6 +201,14 @@ class BroadcastQueueModernImpl extends BroadcastQueue { private final BroadcastConstants mFgConstants; private final BroadcastConstants mBgConstants; + /** + * This single object allows the queue to dispatch receivers using scheduleReceiverList + * without constantly allocating new ReceiverInfo objects or ArrayLists. This queue + * implementation is known to have a maximum size of one entry. + */ + @VisibleForTesting + final BroadcastReceiverBatch mReceiverBatch = new BroadcastReceiverBatch(1); + /** * Timestamp when last {@link #testAllProcessQueues} failure was observed; * used for throttling log messages. @@ -763,7 +771,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { * Examine a receiver and possibly skip it. The method returns true if the receiver is * skipped (and therefore no more work is required). */ - private boolean maybeSkipReceiver(BroadcastProcessQueue queue, BroadcastRecord r, int index) { + private boolean maybeSkipReceiver(@NonNull BroadcastProcessQueue queue, + @NonNull BroadcastRecord r, int index) { final int oldDeliveryState = getDeliveryState(r, index); final ProcessRecord app = queue.app; final Object receiver = r.receivers.get(index); @@ -803,14 +812,15 @@ class BroadcastQueueModernImpl extends BroadcastQueue { /** * Return true if this receiver should be assumed to have been delivered. */ - private boolean isAssumedDelivered(BroadcastRecord r, int index) { + private boolean isAssumedDelivered(@NonNull BroadcastRecord r, int index) { return (r.receivers.get(index) instanceof BroadcastFilter) && !r.ordered; } /** * A receiver is about to be dispatched. Start ANR timers, if necessary. */ - private void dispatchReceivers(BroadcastProcessQueue queue, BroadcastRecord r, int index) { + private void dispatchReceivers(@NonNull BroadcastProcessQueue queue, + @NonNull BroadcastRecord r, int index) { final ProcessRecord app = queue.app; final Object receiver = r.receivers.get(index); @@ -849,16 +859,16 @@ class BroadcastQueueModernImpl extends BroadcastQueue { setDeliveryState(queue, app, r, index, receiver, BroadcastRecord.DELIVERY_SCHEDULED, "scheduleReceiverWarmLocked"); - final Intent receiverIntent = r.getReceiverIntent(receiver); final IApplicationThread thread = app.getOnewayThread(); if (thread != null) { try { + final Intent receiverIntent = r.getReceiverIntent(receiver); if (receiver instanceof BroadcastFilter) { notifyScheduleRegisteredReceiver(app, r, (BroadcastFilter) receiver); - thread.scheduleRegisteredReceiver( + thread.scheduleReceiverList(mReceiverBatch.registeredReceiver( ((BroadcastFilter) receiver).receiverList.receiver, receiverIntent, r.resultCode, r.resultData, r.resultExtras, r.ordered, r.initialSticky, - r.userId, app.mState.getReportedProcState()); + r.userId, app.mState.getReportedProcState())); // TODO: consider making registered receivers of unordered // broadcasts report results to detect ANRs @@ -868,9 +878,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } } else { notifyScheduleReceiver(app, r, (ResolveInfo) receiver); - thread.scheduleReceiver(receiverIntent, ((ResolveInfo) receiver).activityInfo, + thread.scheduleReceiverList(mReceiverBatch.manifestReceiver( + receiverIntent, ((ResolveInfo) receiver).activityInfo, null, r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId, - app.mState.getReportedProcState()); + app.mState.getReportedProcState())); } } catch (RemoteException e) { final String msg = "Failed to schedule " + r + " to " + receiver @@ -898,9 +909,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue { mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily( app, OOM_ADJ_REASON_FINISH_RECEIVER); try { - thread.scheduleRegisteredReceiver(r.resultTo, r.intent, + thread.scheduleReceiverList(mReceiverBatch.registeredReceiver(r.resultTo, r.intent, r.resultCode, r.resultData, r.resultExtras, false, r.initialSticky, - r.userId, app.mState.getReportedProcState()); + r.userId, app.mState.getReportedProcState())); } catch (RemoteException e) { final String msg = "Failed to schedule result of " + r + " via " + app + ": " + e; logw(msg); diff --git a/services/core/java/com/android/server/am/BroadcastReceiverBatch.java b/services/core/java/com/android/server/am/BroadcastReceiverBatch.java new file mode 100644 index 0000000000000..a8264582b8b36 --- /dev/null +++ b/services/core/java/com/android/server/am/BroadcastReceiverBatch.java @@ -0,0 +1,346 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ReceiverInfo; +import android.content.Intent; +import android.content.IIntentReceiver; +import android.content.pm.ActivityInfo; +import android.content.res.CompatibilityInfo; +import android.os.Bundle; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * A batch of receiver instructions. This includes a list of finish requests and a list of + * receivers. The instructions are for a single queue. It is constructed and consumed in a single + * call to {@link BroadcastQueueModernImpl#scheduleReceiverWarmLocked}. The list size is bounded by + * {@link BroadcastConstants#MAX_BROADCAST_BATCH_SIZE}. Because this class is ephemeral and its use + * is bounded, it is pre-allocated to avoid allocating new objects every time it is used. + * + * The {@link #single} methods support the use of this class in {@link BroadcastQueueImpl}. These + * methods simplify the use of {@link IApplicationThread#scheduleReceiverList} as a replacement + * for scheduleReceiver and scheduleRegisteredReceiver. + * + * This class is designed to be allocated once and constantly reused (call {@link #reset} between + * uses). Objects needed by the instance are kept in a pool - all object allocation occurs when + * the instance is created, and no allocation occurs thereafter. However, if the variable + * mDeepReceiverCopy is set true (it is false by default) then the method {@link #receivers} always + * returns newly allocated objects. This is required for mock testing. + * + * This class is not thread-safe. Instances must be protected by the caller. + * @hide + */ +final class BroadcastReceiverBatch { + + /** + * If this is true then receivers() returns a deep copy of the ReceiveInfo array. If this is + * false, receivers() returns a reference to the array. A deep copy is needed only for the + * broadcast queue mocking tests. + */ + @VisibleForTesting + boolean mDeepReceiverCopy = false; + + /** + * A private pool implementation this class. + */ + private static class Pool { + final int size; + final ArrayList pool; + int next; + Pool(int n, @NonNull Class c) { + size = n; + pool = new ArrayList<>(size); + try { + for (int i = 0; i < size; i++) { + pool.add(c.getDeclaredConstructor().newInstance()); + } + } catch (Exception e) { + // This class is only used locally. Turn any exceptions into something fatal. + throw new RuntimeException(e); + } + } + T next() { + return pool.get(next++); + } + void reset() { + next = 0; + } + } + + /** + * The information needed to finish off a receiver. This is valid only in the context of + * a queue. + */ + static class FinishInfo { + BroadcastRecord r = null; + int index = 0; + int deliveryState = 0; + String reason = null; + FinishInfo set(@Nullable BroadcastRecord r, int index, + int deliveryState, @Nullable String reason) { + this.r = r; + this.index = index; + this.deliveryState = deliveryState; + this.reason = reason; + return this; + } + } + + /** + * The information needed to recreate a receiver info. The broadcast record can be null if the + * caller does not expect to need it later. + */ + static class ReceiverCookie { + BroadcastRecord r = null; + int index = 0; + ReceiverCookie set(@Nullable BroadcastRecord r, int index) { + this.r = r; + this.index = index; + return this; + } + } + + // The object pools. + final int mSize; + private final Pool receiverPool; + private final Pool finishPool; + private final Pool cookiePool; + + // The accumulated data. The receivers should be an ArrayList to be directly compatible + // with scheduleReceiverList(). The receivers array is not final because a new array must + // be created for every new call to scheduleReceiverList(). + private final ArrayList mReceivers; + private final ArrayList mCookies; + private final ArrayList mFinished; + // The list of finish records to complete if the binder succeeds or fails. + private final ArrayList mSuccess; + + BroadcastReceiverBatch(int size) { + mSize = size; + mReceivers = new ArrayList<>(mSize); + mCookies = new ArrayList<>(mSize); + mFinished = new ArrayList<>(mSize); + mSuccess = new ArrayList<>(mSize); + + receiverPool = new Pool<>(mSize, ReceiverInfo.class); + finishPool = new Pool<>(mSize, FinishInfo.class); + cookiePool = new Pool<>(mSize, ReceiverCookie.class); + mStats = new Statistics(mSize); + reset(); + } + + void reset() { + mReceivers.clear(); + mCookies.clear(); + mFinished.clear(); + mSuccess.clear(); + + receiverPool.reset(); + finishPool.reset(); + cookiePool.reset(); + } + + void finish(@Nullable BroadcastRecord r, int index, + int deliveryState, @Nullable String reason) { + mFinished.add(finishPool.next().set(r, index, deliveryState, reason)); + } + void success(@Nullable BroadcastRecord r, int index, + int deliveryState, @Nullable String reason) { + mSuccess.add(finishPool.next().set(r, index, deliveryState, reason)); + } + // Add a ReceiverInfo for a registered receiver. + void schedule(@Nullable IIntentReceiver receiver, Intent intent, + int resultCode, @Nullable String data, @Nullable Bundle extras, boolean ordered, + boolean sticky, int sendingUser, int processState, + @Nullable BroadcastRecord r, int index) { + ReceiverInfo ri = new ReceiverInfo(); + ri.intent = intent; + ri.data = data; + ri.extras = extras; + ri.sendingUser = sendingUser; + ri.processState = processState; + ri.resultCode = resultCode; + ri.registered = true; + ri.receiver = receiver; + ri.ordered = ordered; + ri.sticky = sticky; + mReceivers.add(ri); + mCookies.add(cookiePool.next().set(r, index)); + } + // Add a ReceiverInfo for a manifest receiver. + void schedule(@Nullable Intent intent, @Nullable ActivityInfo activityInfo, + @Nullable CompatibilityInfo compatInfo, int resultCode, @Nullable String data, + @Nullable Bundle extras, boolean sync, int sendingUser, int processState, + @Nullable BroadcastRecord r, int index) { + ReceiverInfo ri = new ReceiverInfo(); + ri.intent = intent; + ri.data = data; + ri.extras = extras; + ri.sendingUser = sendingUser; + ri.processState = processState; + ri.resultCode = resultCode; + ri.registered = false; + ri.activityInfo = activityInfo; + ri.compatInfo = compatInfo; + ri.sync = sync; + mReceivers.add(ri); + mCookies.add(cookiePool.next().set(r, index)); + } + + /** + * Two convenience functions for dispatching a single receiver. The functions start with a + * reset. Then they create the ReceiverInfo array and return it. Statistics are not + * collected. + */ + ArrayList registeredReceiver(@Nullable IIntentReceiver receiver, + @Nullable Intent intent, int resultCode, @Nullable String data, + @Nullable Bundle extras, boolean ordered, boolean sticky, + int sendingUser, int processState) { + reset(); + schedule(receiver, intent, resultCode, data, extras, ordered, sticky, + sendingUser, processState, null, 0); + return receivers(); + } + + ArrayList manifestReceiver(@Nullable Intent intent, + @Nullable ActivityInfo activityInfo, @Nullable CompatibilityInfo compatInfo, + int resultCode, @Nullable String data, @Nullable Bundle extras, boolean sync, + int sendingUser, int processState) { + reset(); + schedule(intent, activityInfo, compatInfo, resultCode, data, extras, sync, + sendingUser, processState, null, 0); + return receivers(); + } + + // Return true if the batch is full. Adding any more entries will throw an exception. + boolean isFull() { + return (mFinished.size() + mReceivers.size()) >= mSize; + } + int finishCount() { + return mFinished.size() + mSuccess.size(); + } + int receiverCount() { + return mReceivers.size(); + } + + /** + * Create a deep copy of the receiver list. This is only for testing which is confused when + * objects are reused. + */ + private ArrayList copyReceiverInfo() { + ArrayList copy = new ArrayList<>(); + for (int i = 0; i < mReceivers.size(); i++) { + final ReceiverInfo r = mReceivers.get(i); + final ReceiverInfo n = new ReceiverInfo(); + n.intent = r.intent; + n.data = r.data; + n.extras = r.extras; + n.sendingUser = r.sendingUser; + n.processState = r.processState; + n.resultCode = r.resultCode; + n.registered = r.registered; + n.receiver = r.receiver; + n.ordered = r.ordered; + n.sticky = r.sticky; + n.activityInfo = r.activityInfo; + n.compatInfo = r.compatInfo; + n.sync = r.sync; + copy.add(n); + } + return copy; + } + + /** + * Accessors for the accumulated instructions. The important accessor is receivers(), since + * it can be modified to return a deep copy of the mReceivers array. + */ + @NonNull + ArrayList receivers() { + if (!mDeepReceiverCopy) { + return mReceivers; + } else { + return copyReceiverInfo(); + } + } + @NonNull + ArrayList cookies() { + return mCookies; + } + @NonNull + ArrayList finished() { + return mFinished; + } + @NonNull + ArrayList success() { + return mSuccess; + } + + /** + * A simple POD for statistics. The parameter is the size of the BroadcastReceiverBatch. + */ + static class Statistics { + final int[] finish; + final int[] local; + final int[] remote; + Statistics(int size) { + finish = new int[size+1]; + local = new int[size+1]; + remote = new int[size+1]; + } + } + + private final Statistics mStats; + + /** + * A unique counter that identifies individual transmission groups. This is only used for + * debugging. It is used to determine which receivers were sent in the same batch, and in + * which order. This is static to distinguish between batches across all queues in the + * system. + */ + private static final AtomicInteger sTransmitGroup = new AtomicInteger(0); + + /** + * Record statistics for this batch of instructions. This updates the local statistics and it + * updates the transmitGroup and transmitOrder fields of the BroadcastRecords being + * dispatched. + */ + void recordBatch(boolean local) { + final int group = sTransmitGroup.addAndGet(1); + for (int i = 0; i < cookies().size(); i++) { + final var cookie = cookies().get(i); + cookie.r.transmitGroup[cookie.index] = group; + cookie.r.transmitOrder[cookie.index] = i; + } + mStats.finish[finishCount()]++; + if (local) { + mStats.local[receiverCount()]++; + } else { + mStats.remote[receiverCount()]++; + } + } + + @NonNull + Statistics getStatistics() { + return mStats; + } +} diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java index 100b2db691de4..24cf3d2800638 100644 --- a/services/core/java/com/android/server/am/BroadcastRecord.java +++ b/services/core/java/com/android/server/am/BroadcastRecord.java @@ -113,6 +113,8 @@ final class BroadcastRecord extends Binder { @UptimeMillisLong long finishTime; // when broadcast finished final @UptimeMillisLong long[] scheduledTime; // when each receiver was scheduled final @UptimeMillisLong long[] terminalTime; // when each receiver was terminal + final int[] transmitGroup; // the batch group for each receiver + final int[] transmitOrder; // the position of the receiver in the group final boolean timeoutExempt; // true if this broadcast is not subject to receiver timeouts int resultCode; // current result code value. @Nullable String resultData; // current result data value. @@ -380,6 +382,8 @@ final class BroadcastRecord extends Binder { blockedUntilTerminalCount = calculateBlockedUntilTerminalCount(receivers, _serialized); scheduledTime = new long[delivery.length]; terminalTime = new long[delivery.length]; + transmitGroup = new int[delivery.length]; + transmitOrder = new int[delivery.length]; resultToApp = _resultToApp; resultTo = _resultTo; resultCode = _resultCode; @@ -433,6 +437,8 @@ final class BroadcastRecord extends Binder { blockedUntilTerminalCount = from.blockedUntilTerminalCount; scheduledTime = from.scheduledTime; terminalTime = from.terminalTime; + transmitGroup = from.transmitGroup; + transmitOrder = from.transmitOrder; resultToApp = from.resultToApp; resultTo = from.resultTo; enqueueTime = from.enqueueTime; 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 b9615f6b3771c..d79c4d8d83af8 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -50,6 +50,7 @@ import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.BroadcastOptions; import android.app.IApplicationThread; +import android.app.ReceiverInfo; import android.app.usage.UsageEvents.Event; import android.app.usage.UsageStatsManagerInternal; import android.content.ComponentName; @@ -62,6 +63,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; +import android.content.res.CompatibilityInfo; import android.os.Binder; import android.os.Bundle; import android.os.DeadObjectException; @@ -262,6 +264,7 @@ public class BroadcastQueueTest { return res; }).when(mAms).startProcessLocked(any(), any(), anyBoolean(), anyInt(), any(), anyInt(), anyBoolean(), anyBoolean()); + doAnswer((invocation) -> { final String processName = invocation.getArgument(0); final int uid = invocation.getArgument(1); @@ -295,12 +298,16 @@ public class BroadcastQueueTest { }; if (mImpl == Impl.DEFAULT) { - mQueue = new BroadcastQueueImpl(mAms, mHandlerThread.getThreadHandler(), TAG, + var q = new BroadcastQueueImpl(mAms, mHandlerThread.getThreadHandler(), TAG, constants, emptySkipPolicy, emptyHistory, false, ProcessList.SCHED_GROUP_DEFAULT); + q.mReceiverBatch.mDeepReceiverCopy = true; + mQueue = q; } else if (mImpl == Impl.MODERN) { - mQueue = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(), + var q = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(), constants, constants, emptySkipPolicy, emptyHistory); + q.mReceiverBatch.mDeepReceiverCopy = true; + mQueue = q; } else { throw new UnsupportedOperationException(); } @@ -398,6 +405,43 @@ public class BroadcastQueueTest { UnaryOperator.identity()); } + private void doRegisteredReceiver(ProcessRecord r, boolean wedge, boolean abort, + UnaryOperator extrasOperator, ReceiverInfo info) { + final Intent intent = info.intent; + final Bundle extras = info.extras; + final boolean ordered = info.ordered; + mScheduledBroadcasts.add(makeScheduledBroadcast(r, intent)); + if (!wedge && ordered) { + assertTrue(r.mReceivers.numberOfCurReceivers() > 0); + assertNotEquals(ProcessList.SCHED_GROUP_UNDEFINED, + mQueue.getPreferredSchedulingGroupLocked(r)); + mHandlerThread.getThreadHandler().post(() -> { + synchronized (mAms) { + mQueue.finishReceiverLocked(r, Activity.RESULT_OK, + null, extrasOperator.apply(extras), abort, false); + } + }); + } + } + + private void doManifestReceiver(ProcessRecord r, boolean wedge, boolean abort, + UnaryOperator extrasOperator, ReceiverInfo info) { + final Intent intent = info.intent; + final Bundle extras = info.extras; + mScheduledBroadcasts.add(makeScheduledBroadcast(r, intent)); + if (!wedge) { + assertTrue(r.mReceivers.numberOfCurReceivers() > 0); + assertNotEquals(ProcessList.SCHED_GROUP_UNDEFINED, + mQueue.getPreferredSchedulingGroupLocked(r)); + mHandlerThread.getThreadHandler().post(() -> { + synchronized (mAms) { + mQueue.finishReceiverLocked(r, Activity.RESULT_OK, null, + extrasOperator.apply(extras), abort, false); + } + }); + } + } + private ProcessRecord makeActiveProcessRecord(ApplicationInfo ai, String processName, ProcessBehavior behavior, UnaryOperator extrasOperator) throws Exception { final boolean wedge = (behavior == ProcessBehavior.WEDGE); @@ -441,47 +485,22 @@ public class BroadcastQueueTest { if (dead) return r; doAnswer((invocation) -> { - Log.v(TAG, "Intercepting scheduleReceiver() for " + Log.v(TAG, "Intercepting scheduleReceiverList() for " + Arrays.toString(invocation.getArguments())); - final Intent intent = invocation.getArgument(0); - final Bundle extras = invocation.getArgument(5); - mScheduledBroadcasts.add(makeScheduledBroadcast(r, intent)); - if (!wedge) { - assertTrue(r.mReceivers.numberOfCurReceivers() > 0); - assertNotEquals(ProcessList.SCHED_GROUP_UNDEFINED, - mQueue.getPreferredSchedulingGroupLocked(r)); - mHandlerThread.getThreadHandler().post(() -> { - synchronized (mAms) { - mQueue.finishReceiverLocked(r, Activity.RESULT_OK, null, - extrasOperator.apply(extras), abort, false); - } - }); + final List data = invocation.getArgument(0); + for (int i = 0; i < data.size(); i++) { + ReceiverInfo info = data.get(i); + // The logic here mimics the logic in ActivityThread: elements of the list are + // forwarded to a handler for manifest receivers or to a handler for registered + // receivers. + if (info.registered) { + doRegisteredReceiver(r, wedge, abort, extrasOperator, info); + } else { + doManifestReceiver(r, wedge, abort, extrasOperator, info); + } } return null; - }).when(thread).scheduleReceiver(any(), any(), any(), anyInt(), any(), any(), anyBoolean(), - anyInt(), anyInt()); - - doAnswer((invocation) -> { - Log.v(TAG, "Intercepting scheduleRegisteredReceiver() for " - + Arrays.toString(invocation.getArguments())); - final Intent intent = invocation.getArgument(1); - final Bundle extras = invocation.getArgument(4); - final boolean ordered = invocation.getArgument(5); - mScheduledBroadcasts.add(makeScheduledBroadcast(r, intent)); - if (!wedge && ordered) { - assertTrue(r.mReceivers.numberOfCurReceivers() > 0); - assertNotEquals(ProcessList.SCHED_GROUP_UNDEFINED, - mQueue.getPreferredSchedulingGroupLocked(r)); - mHandlerThread.getThreadHandler().post(() -> { - synchronized (mAms) { - mQueue.finishReceiverLocked(r, Activity.RESULT_OK, - null, extrasOperator.apply(extras), abort, false); - } - }); - } - return null; - }).when(thread).scheduleRegisteredReceiver(any(), any(), anyInt(), any(), any(), - anyBoolean(), anyBoolean(), anyInt(), anyInt()); + }).when(thread).scheduleReceiverList(any()); return r; } @@ -623,6 +642,123 @@ public class BroadcastQueueTest { }; } + private static boolean matchElement(T a, T b) { + return a == null || a.equals(b); + } + private static boolean matchObject(ArgumentMatcher m, T b) { + return m == null || m.matches(b); + } + + /** + * Create an ArgumentMatcher for a manifest receiver. The parameters are in the order of + * {@link IApplicationThread#scheduleReceiver} but the names correspond to the field names in + * {@link ReceiverInfo}. For every parameter, a null means "don't care". + */ + private ArgumentMatcher manifestReceiverMatcher( + ArgumentMatcher intent, + ArgumentMatcher activityInfo, + ArgumentMatcher compatInfo, + Integer resultCode, + ArgumentMatcher data, + ArgumentMatcher extras, + Boolean sync, + Integer sendingUser, + Integer processState) { + return (test) -> { + return test.registered == false + && matchObject(intent, test.intent) + && matchObject(activityInfo, test.activityInfo) + && matchObject(compatInfo, test.compatInfo) + && matchElement(resultCode, test.resultCode) + && matchObject(data, test.data) + && matchObject(extras, test.extras) + && matchElement(sync, test.sync) + && matchElement(sendingUser, test.sendingUser) + && matchElement(processState, test.processState); + }; + } + + + /** + * Create an argument suitable for the verify() mock methods, when the goal is to find a call + * containing a manifest receiver. + */ + private List manifestReceiver( + ArgumentMatcher intent, + ArgumentMatcher activityInfo, + ArgumentMatcher compatInfo, + Integer resultCode, + ArgumentMatcher data, + ArgumentMatcher extras, + Boolean sync, + Integer sendingUser, + Integer processState) { + return argThat(receiverList(manifestReceiverMatcher(intent, activityInfo, compatInfo, + resultCode, data, extras, sync, sendingUser, processState))); + } + + /** + * Create an ArgumentMatcher for a registered receiver. The parameters are in the order of + * {@link IApplicationThread#scheduleRegisteredReceiver} but the names correspond to the field + * names in {@link ReceiverInfo}. For every parameter, a null means "don't care". + */ + private ArgumentMatcher registeredReceiverMatcher( + ArgumentMatcher receiver, + ArgumentMatcher intent, + Integer resultCode, + ArgumentMatcher data, + ArgumentMatcher extras, + Boolean ordered, + Boolean sticky, + Integer sendingUser, + Integer processState) { + return (test) -> { + return test.registered == true + && matchObject(receiver, test.receiver) + && matchObject(intent, test.intent) + && matchElement(resultCode, test.resultCode) + && matchObject(data, test.data) + && matchObject(extras, test.extras) + && matchElement(ordered, test.ordered) + && matchElement(sticky, test.sticky) + && matchElement(sendingUser, test.sendingUser) + && matchElement(processState, test.processState); + }; + } + + /** + * Create an argument suitable for the verify() mock methods, when the goal is to find a call + * containing a registered receiver. + */ + private List registeredReceiver( + ArgumentMatcher receiver, + ArgumentMatcher intent, + Integer resultCode, + ArgumentMatcher data, + ArgumentMatcher extras, + Boolean ordered, + Boolean sticky, + Integer sendingUser, + Integer processState) { + return argThat(receiverList(registeredReceiverMatcher(receiver, intent, resultCode, + data, extras, ordered, sticky, sendingUser, processState))); + } + + /** + * Apply a matcher to every element in a ReceiverInfo list. + */ + private ArgumentMatcher> receiverList(ArgumentMatcher a) { + return (test) -> { + for (int i = 0; i < test.size(); i++) { + ReceiverInfo r = test.get(i); + if (a.matches(r)) { + return true; + } + } + return false; + }; + } + private ArgumentMatcher bundleEquals(Bundle bundle) { return (test) -> { // TODO: check values in addition to keys @@ -653,27 +789,28 @@ public class BroadcastQueueTest { verifyScheduleReceiver(mode, app, intent, UserHandle.USER_SYSTEM); } - private void verifyScheduleReceiver(VerificationMode mode, ProcessRecord app, Intent intent, - int userId) throws Exception { - verify(app.getThread(), mode).scheduleReceiver( - argThat(filterEqualsIgnoringComponent(intent)), any(), any(), anyInt(), any(), - any(), eq(false), eq(userId), anyInt()); - } - private void verifyScheduleReceiver(VerificationMode mode, ProcessRecord app, Intent intent, ComponentName component) throws Exception { final Intent targetedIntent = new Intent(intent); targetedIntent.setComponent(component); - verify(app.getThread(), mode).scheduleReceiver( - argThat(filterEquals(targetedIntent)), any(), any(), anyInt(), any(), - any(), eq(false), eq(UserHandle.USER_SYSTEM), anyInt()); + verify(app.getThread(), mode).scheduleReceiverList( + manifestReceiver(filterEquals(targetedIntent), + null, null, null, null, null, null, UserHandle.USER_SYSTEM, null)); } - private void verifyScheduleRegisteredReceiver(ProcessRecord app, Intent intent) + private void verifyScheduleReceiver(VerificationMode mode, ProcessRecord app, + Intent intent, int userId) throws Exception { + verify(app.getThread(), mode).scheduleReceiverList( + manifestReceiver(filterEqualsIgnoringComponent(intent), + null, null, null, null, null, null, userId, null)); + } + + private void verifyScheduleRegisteredReceiver(ProcessRecord app, + Intent intent) throws Exception { - verify(app.getThread()).scheduleRegisteredReceiver(any(), - argThat(filterEqualsIgnoringComponent(intent)), anyInt(), any(), any(), - anyBoolean(), anyBoolean(), eq(UserHandle.USER_SYSTEM), anyInt()); + verify(app.getThread()).scheduleReceiverList( + registeredReceiver(null, filterEqualsIgnoringComponent(intent), + null, null, null, null, null, UserHandle.USER_SYSTEM, null)); } static final int USER_GUEST = 11; @@ -1237,23 +1374,24 @@ public class BroadcastQueueTest { final InOrder inOrder = inOrder(greenThread, blueThread, yellowThread, redThread); final Bundle expectedExtras = new Bundle(); expectedExtras.putBoolean(PACKAGE_RED, true); - inOrder.verify(greenThread).scheduleReceiver( - argThat(filterEqualsIgnoringComponent(airplane)), any(), any(), - eq(Activity.RESULT_OK), any(), argThat(bundleEquals(expectedExtras)), eq(true), - eq(UserHandle.USER_SYSTEM), anyInt()); - inOrder.verify(blueThread).scheduleReceiver( - argThat(filterEqualsIgnoringComponent(airplane)), any(), any(), - eq(Activity.RESULT_OK), any(), argThat(bundleEquals(expectedExtras)), eq(true), - eq(UserHandle.USER_SYSTEM), anyInt()); + inOrder.verify(greenThread).scheduleReceiverList(manifestReceiver( + filterEqualsIgnoringComponent(airplane), null, null, + Activity.RESULT_OK, null, bundleEquals(expectedExtras), true, + UserHandle.USER_SYSTEM, null)); + inOrder.verify(blueThread).scheduleReceiverList(manifestReceiver( + filterEqualsIgnoringComponent(airplane), null, null, + Activity.RESULT_OK, null, bundleEquals(expectedExtras), true, + UserHandle.USER_SYSTEM, null)); expectedExtras.putBoolean(PACKAGE_BLUE, true); - inOrder.verify(yellowThread).scheduleReceiver( - argThat(filterEqualsIgnoringComponent(airplane)), any(), any(), - eq(Activity.RESULT_OK), any(), argThat(bundleEquals(expectedExtras)), eq(true), - eq(UserHandle.USER_SYSTEM), anyInt()); + inOrder.verify(yellowThread).scheduleReceiverList(manifestReceiver( + filterEqualsIgnoringComponent(airplane), null, null, + Activity.RESULT_OK, null, bundleEquals(expectedExtras), true, + UserHandle.USER_SYSTEM, null)); expectedExtras.putBoolean(PACKAGE_YELLOW, true); - inOrder.verify(redThread).scheduleRegisteredReceiver(any(), argThat(filterEquals(airplane)), - eq(Activity.RESULT_OK), any(), argThat(bundleEquals(expectedExtras)), eq(false), - anyBoolean(), eq(UserHandle.USER_SYSTEM), anyInt()); + inOrder.verify(redThread).scheduleReceiverList(registeredReceiver( + null, filterEquals(airplane), + Activity.RESULT_OK, null, bundleEquals(expectedExtras), false, + null, UserHandle.USER_SYSTEM, null)); // Finally, verify that we thawed the final receiver verify(mAms.mOomAdjuster.mCachedAppOptimizer).unfreezeTemporarily(eq(callerApp), @@ -1316,22 +1454,24 @@ public class BroadcastQueueTest { // have invoked or skipped the second receiver depending on the intent // flag policy; we always deliver to final receiver regardless of abort final InOrder inOrder = inOrder(greenThread, blueThread, redThread); - inOrder.verify(greenThread).scheduleReceiver( - argThat(filterEqualsIgnoringComponent(intent)), any(), any(), - eq(Activity.RESULT_OK), any(), any(), eq(true), eq(UserHandle.USER_SYSTEM), - anyInt()); + inOrder.verify(greenThread).scheduleReceiverList(manifestReceiver( + filterEqualsIgnoringComponent(intent), null, null, + Activity.RESULT_OK, null, null, true, UserHandle.USER_SYSTEM, + null)); if ((intent.getFlags() & Intent.FLAG_RECEIVER_NO_ABORT) != 0) { - inOrder.verify(blueThread).scheduleReceiver( - argThat(filterEqualsIgnoringComponent(intent)), any(), any(), - eq(Activity.RESULT_OK), any(), any(), eq(true), eq(UserHandle.USER_SYSTEM), - anyInt()); + inOrder.verify(blueThread).scheduleReceiverList(manifestReceiver( + filterEqualsIgnoringComponent(intent), null, null, + Activity.RESULT_OK, null, null, true, UserHandle.USER_SYSTEM, + null)); } else { - inOrder.verify(blueThread, never()).scheduleReceiver(any(), any(), any(), anyInt(), - any(), any(), anyBoolean(), anyInt(), anyInt()); + inOrder.verify(blueThread, never()).scheduleReceiverList(manifestReceiver( + null, null, null, null, + null, null, null, null, null)); } - inOrder.verify(redThread).scheduleRegisteredReceiver(any(), argThat(filterEquals(intent)), - eq(Activity.RESULT_OK), any(), argThat(bundleEquals(expectedExtras)), - eq(false), anyBoolean(), eq(UserHandle.USER_SYSTEM), anyInt()); + inOrder.verify(redThread).scheduleReceiverList(registeredReceiver( + null, filterEquals(intent), + Activity.RESULT_OK, null, bundleEquals(expectedExtras), + false, null, UserHandle.USER_SYSTEM, null)); } /** @@ -1351,9 +1491,10 @@ public class BroadcastQueueTest { orderedResultTo, orderedExtras)); waitForIdle(); - verify(callerThread).scheduleRegisteredReceiver(any(), argThat(filterEquals(airplane)), - eq(Activity.RESULT_OK), any(), argThat(bundleEquals(orderedExtras)), eq(false), - anyBoolean(), eq(UserHandle.USER_SYSTEM), anyInt()); + verify(callerThread).scheduleReceiverList(registeredReceiver( + null, filterEquals(airplane), + Activity.RESULT_OK, null, bundleEquals(orderedExtras), false, + null, UserHandle.USER_SYSTEM, null)); } /** @@ -1371,9 +1512,10 @@ public class BroadcastQueueTest { makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE)), resultTo)); waitForIdle(); - verify(callerThread).scheduleRegisteredReceiver(any(), argThat(filterEquals(airplane)), - eq(Activity.RESULT_OK), any(), any(), eq(false), - anyBoolean(), eq(UserHandle.USER_SYSTEM), anyInt()); + verify(callerThread).scheduleReceiverList(registeredReceiver( + null, filterEquals(airplane), + Activity.RESULT_OK, null, null, false, + null, UserHandle.USER_SYSTEM, null)); } /** @@ -1536,28 +1678,28 @@ public class BroadcastQueueTest { final InOrder inOrder = inOrder(callerThread, blueThread); // First broadcast is canceled - inOrder.verify(callerThread).scheduleRegisteredReceiver(any(), - argThat(filterAndExtrasEquals(timezoneFirst)), eq(Activity.RESULT_CANCELED), any(), - any(), eq(false), anyBoolean(), eq(UserHandle.USER_SYSTEM), anyInt()); + inOrder.verify(callerThread).scheduleReceiverList(registeredReceiver(null, + filterAndExtrasEquals(timezoneFirst), Activity.RESULT_CANCELED, null, + null, false, null, UserHandle.USER_SYSTEM, null)); // We deliver second broadcast to app timezoneSecond.setClassName(PACKAGE_BLUE, CLASS_GREEN); - inOrder.verify(blueThread).scheduleReceiver( - argThat(filterAndExtrasEquals(timezoneSecond)), - any(), any(), anyInt(), any(), any(), eq(true), anyInt(), anyInt()); + inOrder.verify(blueThread).scheduleReceiverList(manifestReceiver( + filterAndExtrasEquals(timezoneSecond), + null, null, null, null, null, true, null, null)); // Second broadcast is finished timezoneSecond.setComponent(null); - inOrder.verify(callerThread).scheduleRegisteredReceiver(any(), - argThat(filterAndExtrasEquals(timezoneSecond)), eq(Activity.RESULT_OK), any(), - any(), eq(false), anyBoolean(), eq(UserHandle.USER_SYSTEM), anyInt()); + inOrder.verify(callerThread).scheduleReceiverList(registeredReceiver(null, + filterAndExtrasEquals(timezoneSecond), Activity.RESULT_OK, null, + null, false, null, UserHandle.USER_SYSTEM, null)); // Since we "replaced" the first broadcast in its original position, // only now do we see the airplane broadcast airplane.setClassName(PACKAGE_BLUE, CLASS_RED); - inOrder.verify(blueThread).scheduleReceiver( - argThat(filterEquals(airplane)), - any(), any(), anyInt(), any(), any(), eq(false), anyInt(), anyInt()); + inOrder.verify(blueThread).scheduleReceiverList(manifestReceiver( + filterEquals(airplane), + null, null, null, null, null, false, null, null)); } @Test