Loading services/core/java/com/android/server/am/BroadcastConstants.java +12 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); } } Loading Loading @@ -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(); } Loading services/core/java/com/android/server/am/BroadcastQueueImpl.java +15 −4 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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) { Loading Loading @@ -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; Loading Loading @@ -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) { Loading services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +21 −10 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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 Loading @@ -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 Loading Loading @@ -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); Loading services/core/java/com/android/server/am/BroadcastReceiverBatch.java 0 → 100644 +346 −0 Original line number Diff line number Diff line /* * 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<T> { final int size; final ArrayList<T> pool; int next; Pool(int n, @NonNull Class<T> 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<ReceiverInfo> receiverPool; private final Pool<FinishInfo> finishPool; private final Pool<ReceiverCookie> 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<ReceiverInfo> mReceivers; private final ArrayList<ReceiverCookie> mCookies; private final ArrayList<FinishInfo> mFinished; // The list of finish records to complete if the binder succeeds or fails. private final ArrayList<FinishInfo> 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<ReceiverInfo> 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<ReceiverInfo> 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<ReceiverInfo> copyReceiverInfo() { ArrayList<ReceiverInfo> 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<ReceiverInfo> receivers() { if (!mDeepReceiverCopy) { return mReceivers; } else { return copyReceiverInfo(); } } @NonNull ArrayList<ReceiverCookie> cookies() { return mCookies; } @NonNull ArrayList<FinishInfo> finished() { return mFinished; } @NonNull ArrayList<FinishInfo> 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; } } services/core/java/com/android/server/am/BroadcastRecord.java +6 −0 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading
services/core/java/com/android/server/am/BroadcastConstants.java +12 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); } } Loading Loading @@ -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(); } Loading
services/core/java/com/android/server/am/BroadcastQueueImpl.java +15 −4 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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) { Loading Loading @@ -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; Loading Loading @@ -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) { Loading
services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +21 −10 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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 Loading @@ -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 Loading Loading @@ -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); Loading
services/core/java/com/android/server/am/BroadcastReceiverBatch.java 0 → 100644 +346 −0 Original line number Diff line number Diff line /* * 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<T> { final int size; final ArrayList<T> pool; int next; Pool(int n, @NonNull Class<T> 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<ReceiverInfo> receiverPool; private final Pool<FinishInfo> finishPool; private final Pool<ReceiverCookie> 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<ReceiverInfo> mReceivers; private final ArrayList<ReceiverCookie> mCookies; private final ArrayList<FinishInfo> mFinished; // The list of finish records to complete if the binder succeeds or fails. private final ArrayList<FinishInfo> 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<ReceiverInfo> 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<ReceiverInfo> 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<ReceiverInfo> copyReceiverInfo() { ArrayList<ReceiverInfo> 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<ReceiverInfo> receivers() { if (!mDeepReceiverCopy) { return mReceivers; } else { return copyReceiverInfo(); } } @NonNull ArrayList<ReceiverCookie> cookies() { return mCookies; } @NonNull ArrayList<FinishInfo> finished() { return mFinished; } @NonNull ArrayList<FinishInfo> 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; } }
services/core/java/com/android/server/am/BroadcastRecord.java +6 −0 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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; Loading Loading @@ -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; Loading