Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d0f896ac authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

BroadcastQueue: more robust edge-case handling.

When a caller provides a null receivers list, simply swap in an
immutable empty list so we get stronger @NonNull behavior.  Fix bug
when an ordered broadcast is sent to an empty list by sending the
final "resultTo" immediately.

Verify that we handle unexpected finishReceiverLocked() calls coming
in from apps, likely due to them racing with an ANR that finished
the active broadcast on their behalf.

Bug: 249997101
Test: atest FrameworksMockingServicesTests:BroadcastQueueTest
Test: atest FrameworksMockingServicesTests:BroadcastQueueModernImplTest
Change-Id: I50e6bb5878ef028c73352ccf04d8d57c113a6310
parent 02ec70aa
Loading
Loading
Loading
Loading
+10 −3
Original line number Diff line number Diff line
@@ -485,9 +485,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
    public void enqueueBroadcastLocked(@NonNull BroadcastRecord r) {
        r.applySingletonPolicy(mService);

        // 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) {
@@ -511,6 +508,11 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
            updateRunnableList(queue);
            enqueueUpdateRunningList();
        }

        // If nothing to dispatch, send any pending result immediately
        if (r.receivers.isEmpty()) {
            scheduleResultTo(r);
        }
    }

    /**
@@ -702,6 +704,11 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
            @Nullable String resultData, @Nullable Bundle resultExtras, boolean resultAbort,
            boolean waitForServices) {
        final BroadcastProcessQueue queue = getProcessQueue(app);
        if ((queue == null) || !queue.isActive()) {
            logw("Ignoring finish; no active broadcast for " + queue);
            return false;
        }

        final BroadcastRecord r = queue.getActive();
        r.resultCode = resultCode;
        r.resultData = resultData;
+5 −2
Original line number Diff line number Diff line
@@ -89,7 +89,7 @@ final class BroadcastRecord extends Binder {
    final String[] excludedPackages; // packages to exclude
    final int appOp;        // an app op that is associated with this broadcast
    final BroadcastOptions options; // BroadcastOptions supplied by caller
    final List receivers;   // contains BroadcastFilter and ResolveInfo
    final @NonNull List<Object> receivers;   // contains BroadcastFilter and ResolveInfo
    final @DeliveryState int[] delivery;   // delivery state of each receiver
    IIntentReceiver resultTo; // who receives final result if non-null
    boolean deferred;
@@ -133,6 +133,9 @@ final class BroadcastRecord extends Binder {
    String cachedToString;
    String cachedToShortString;

    /** Empty immutable list of receivers */
    static final List<Object> EMPTY_RECEIVERS = List.of();

    static final int IDLE = 0;
    static final int APP_RECEIVE = 1;
    static final int CALL_IN_RECEIVE = 2;
@@ -361,7 +364,7 @@ final class BroadcastRecord extends Binder {
        excludedPackages = _excludedPackages;
        appOp = _appOp;
        options = _options;
        receivers = _receivers;
        receivers = (_receivers != null) ? _receivers : EMPTY_RECEIVERS;
        delivery = new int[_receivers != null ? _receivers.size() : 0];
        scheduledTime = new long[delivery.length];
        terminalTime = new long[delivery.length];
+37 −6
Original line number Diff line number Diff line
@@ -109,7 +109,6 @@ import java.util.function.UnaryOperator;
 */
@MediumTest
@RunWith(Parameterized.class)
@SuppressWarnings("GuardedBy")
public class BroadcastQueueTest {
    private static final String TAG = "BroadcastQueueTest";

@@ -454,31 +453,31 @@ public class BroadcastQueueTest {
    }

    private BroadcastRecord makeBroadcastRecord(Intent intent, ProcessRecord callerApp,
            List receivers) {
            List<Object> receivers) {
        return makeBroadcastRecord(intent, callerApp, BroadcastOptions.makeBasic(),
                receivers, false, null, null, UserHandle.USER_SYSTEM);
    }

    private BroadcastRecord makeBroadcastRecord(Intent intent, ProcessRecord callerApp,
            int userId, List receivers) {
            int userId, List<Object> receivers) {
        return makeBroadcastRecord(intent, callerApp, BroadcastOptions.makeBasic(),
                receivers, false, null, null, userId);
    }

    private BroadcastRecord makeOrderedBroadcastRecord(Intent intent, ProcessRecord callerApp,
            List receivers, IIntentReceiver orderedResultTo, Bundle orderedExtras) {
            List<Object> receivers, IIntentReceiver orderedResultTo, Bundle orderedExtras) {
        return makeBroadcastRecord(intent, callerApp, BroadcastOptions.makeBasic(),
                receivers, true, orderedResultTo, orderedExtras, UserHandle.USER_SYSTEM);
    }

    private BroadcastRecord makeBroadcastRecord(Intent intent, ProcessRecord callerApp,
            BroadcastOptions options, List receivers) {
            BroadcastOptions options, List<Object> receivers) {
        return makeBroadcastRecord(intent, callerApp, options,
                receivers, false, null, null, UserHandle.USER_SYSTEM);
    }

    private BroadcastRecord makeBroadcastRecord(Intent intent, ProcessRecord callerApp,
            BroadcastOptions options, List receivers, boolean ordered,
            BroadcastOptions options, List<Object> receivers, boolean ordered,
            IIntentReceiver orderedResultTo, Bundle orderedExtras, int userId) {
        return new BroadcastRecord(mQueue, intent, callerApp, callerApp.info.packageName, null,
                callerApp.getPid(), callerApp.info.uid, false, null, null, null, null,
@@ -1146,6 +1145,38 @@ public class BroadcastQueueTest {
                eq(false), anyBoolean(), eq(UserHandle.USER_SYSTEM), anyInt());
    }

    /**
     * Verify that we immediately dispatch final result for empty lists.
     */
    @Test
    public void testOrdered_Empty() throws Exception {
        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
        final IApplicationThread callerThread = callerApp.getThread();

        final IIntentReceiver orderedResultTo = mock(IIntentReceiver.class);
        final Bundle orderedExtras = new Bundle();
        orderedExtras.putBoolean(PACKAGE_RED, true);

        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
        enqueueBroadcast(makeOrderedBroadcastRecord(airplane, callerApp, null,
                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 that we're not surprised by a process attempting to finishing a
     * broadcast when none is in progress.
     */
    @Test
    public void testUnexpected() throws Exception {
        final ProcessRecord app = makeActiveProcessRecord(PACKAGE_RED);
        mQueue.finishReceiverLocked(app, Activity.RESULT_OK, null, null, false, false);
    }

    @Test
    public void testBackgroundActivityStarts() throws Exception {
        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);