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

Commit c2ffccd1 authored by Sudheer Shanka's avatar Sudheer Shanka Committed by Android (Google) Code Review
Browse files

Merge "Allow deferring initial delivery of sticky broadcasts." into udc-dev

parents 1c672941 fd41bfcc
Loading
Loading
Loading
Loading
+10 −17
Original line number Diff line number Diff line
@@ -351,6 +351,16 @@ public class BroadcastOptions extends ComponentOptions {
        mDeferralPolicy = opts.getInt(KEY_DEFERRAL_POLICY, DEFERRAL_POLICY_DEFAULT);
    }

    /** @hide */
    @NonNull
    public static BroadcastOptions makeWithDeferUntilActive(boolean deferUntilActive) {
        final BroadcastOptions opts = BroadcastOptions.makeBasic();
        if (deferUntilActive) {
            opts.setDeferralPolicy(DEFERRAL_POLICY_UNTIL_ACTIVE);
        }
        return opts;
    }

    /**
     * Set a duration for which the system should temporary place an application on the
     * power allowlist when this broadcast is being delivered to it.
@@ -774,23 +784,6 @@ public class BroadcastOptions extends ComponentOptions {
        return mIdForResponseEvent;
    }

    /** {@hide} */
    @Deprecated
    public @NonNull BroadcastOptions setDeferUntilActive(boolean shouldDefer) {
        if (shouldDefer) {
            setDeferralPolicy(DEFERRAL_POLICY_UNTIL_ACTIVE);
        } else {
            setDeferralPolicy(DEFERRAL_POLICY_NONE);
        }
        return this;
    }

    /** {@hide} */
    @Deprecated
    public boolean isDeferUntilActive() {
        return (mDeferralPolicy == DEFERRAL_POLICY_UNTIL_ACTIVE);
    }

    /**
     * Sets deferral policy for this broadcast that specifies how this broadcast
     * can be deferred for delivery at some future point.
+1 −1
Original line number Diff line number Diff line
@@ -400,7 +400,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,

    private static final Bundle USER_PRESENT_INTENT_OPTIONS =
            BroadcastOptions.makeBasic()
                    .setDeferUntilActive(true)
                    .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
                    .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
                    .toBundle();

+92 −45
Original line number Diff line number Diff line
@@ -44,7 +44,6 @@ import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_CREATED;
import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
@@ -1169,8 +1168,26 @@ public class ActivityManagerService extends IActivityManager.Stub
     * for stickies that are sent to all users.
     */
    @GuardedBy("this")
    final SparseArray<ArrayMap<String, ArrayList<Intent>>> mStickyBroadcasts =
            new SparseArray<ArrayMap<String, ArrayList<Intent>>>();
    final SparseArray<ArrayMap<String, ArrayList<StickyBroadcast>>> mStickyBroadcasts =
            new SparseArray<>();
    @VisibleForTesting
    static final class StickyBroadcast {
        public Intent intent;
        public boolean deferUntilActive;
        public static StickyBroadcast create(Intent intent, boolean deferUntilActive) {
            final StickyBroadcast b = new StickyBroadcast();
            b.intent = intent;
            b.deferUntilActive = deferUntilActive;
            return b;
        }
        @Override
        public String toString() {
            return "{intent=" + intent + ", defer=" + deferUntilActive + "}";
        }
    }
    final ActiveServices mServices;
@@ -2349,6 +2366,13 @@ public class ActivityManagerService extends IActivityManager.Stub
    /** Provides the basic functionality for unit tests. */
    @VisibleForTesting
    ActivityManagerService(Injector injector, @NonNull ServiceThread handlerThread) {
        this(injector, handlerThread, null);
    }
    /** Provides the basic functionality for unit tests. */
    @VisibleForTesting
    ActivityManagerService(Injector injector, @NonNull ServiceThread handlerThread,
            @Nullable UserController userController) {
        mInjector = injector;
        mContext = mInjector.getContext();
        mUiContext = null;
@@ -2375,7 +2399,7 @@ public class ActivityManagerService extends IActivityManager.Stub
        mSystemThread = null;
        mUiHandler = injector.getUiHandler(null /* service */);
        mUidObserverController = new UidObserverController(mUiHandler);
        mUserController = new UserController(this);
        mUserController = userController == null ? new UserController(this) : userController;
        mInjector.mUserController = mUserController;
        mPendingIntentController =
                new PendingIntentController(handlerThread.getLooper(), mUserController, mConstants);
@@ -10844,12 +10868,12 @@ public class ActivityManagerService extends IActivityManager.Stub
        for (int user=0; user<mStickyBroadcasts.size(); user++) {
            long token = proto.start(ActivityManagerServiceDumpBroadcastsProto.STICKY_BROADCASTS);
            proto.write(StickyBroadcastProto.USER, mStickyBroadcasts.keyAt(user));
            for (Map.Entry<String, ArrayList<Intent>> ent
            for (Map.Entry<String, ArrayList<StickyBroadcast>> ent
                    : mStickyBroadcasts.valueAt(user).entrySet()) {
                long actionToken = proto.start(StickyBroadcastProto.ACTIONS);
                proto.write(StickyBroadcastProto.StickyAction.NAME, ent.getKey());
                for (Intent intent : ent.getValue()) {
                    intent.dumpDebug(proto, StickyBroadcastProto.StickyAction.INTENTS,
                for (StickyBroadcast broadcast : ent.getValue()) {
                    broadcast.intent.dumpDebug(proto, StickyBroadcastProto.StickyAction.INTENTS,
                            false, true, true, false);
                }
                proto.end(actionToken);
@@ -11004,22 +11028,28 @@ public class ActivityManagerService extends IActivityManager.Stub
                pw.print("  Sticky broadcasts for user ");
                        pw.print(mStickyBroadcasts.keyAt(user)); pw.println(":");
                StringBuilder sb = new StringBuilder(128);
                for (Map.Entry<String, ArrayList<Intent>> ent
                for (Map.Entry<String, ArrayList<StickyBroadcast>> ent
                        : mStickyBroadcasts.valueAt(user).entrySet()) {
                    pw.print("  * Sticky action "); pw.print(ent.getKey());
                    if (dumpAll) {
                        pw.println(":");
                        ArrayList<Intent> intents = ent.getValue();
                        final int N = intents.size();
                        ArrayList<StickyBroadcast> broadcasts = ent.getValue();
                        final int N = broadcasts.size();
                        for (int i=0; i<N; i++) {
                            final Intent intent = broadcasts.get(i).intent;
                            final boolean deferUntilActive = broadcasts.get(i).deferUntilActive;
                            sb.setLength(0);
                            sb.append("    Intent: ");
                            intents.get(i).toShortString(sb, false, true, false, false);
                            pw.println(sb.toString());
                            Bundle bundle = intents.get(i).getExtras();
                            intent.toShortString(sb, false, true, false, false);
                            pw.print(sb);
                            if (deferUntilActive) {
                                pw.print(" [D]");
                            }
                            pw.println();
                            Bundle bundle = intent.getExtras();
                            if (bundle != null) {
                                pw.print("      ");
                                pw.println(bundle.toString());
                                pw.print("      extras: ");
                                pw.println(bundle);
                            }
                        }
                    } else {
@@ -13635,7 +13665,7 @@ public class ActivityManagerService extends IActivityManager.Stub
            String callerFeatureId, String receiverId, IIntentReceiver receiver,
            IntentFilter filter, String permission, int userId, int flags) {
        enforceNotIsolatedCaller("registerReceiver");
        ArrayList<Intent> stickyIntents = null;
        ArrayList<StickyBroadcast> stickyBroadcasts = null;
        ProcessRecord callerApp = null;
        final boolean visibleToInstantApps
                = (flags & Context.RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0;
@@ -13711,14 +13741,15 @@ public class ActivityManagerService extends IActivityManager.Stub
            while (actions.hasNext()) {
                String action = actions.next();
                for (int id : userIds) {
                    ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(id);
                    ArrayMap<String, ArrayList<StickyBroadcast>> stickies =
                            mStickyBroadcasts.get(id);
                    if (stickies != null) {
                        ArrayList<Intent> intents = stickies.get(action);
                        if (intents != null) {
                            if (stickyIntents == null) {
                                stickyIntents = new ArrayList<Intent>();
                        ArrayList<StickyBroadcast> broadcasts = stickies.get(action);
                        if (broadcasts != null) {
                            if (stickyBroadcasts == null) {
                                stickyBroadcasts = new ArrayList<>();
                            }
                            stickyIntents.addAll(intents);
                            stickyBroadcasts.addAll(broadcasts);
                        }
                    }
                }
@@ -13799,12 +13830,13 @@ public class ActivityManagerService extends IActivityManager.Stub
        // Dynamic receivers are exported by default for versions prior to T
        final boolean exported = (flags & Context.RECEIVER_EXPORTED) != 0;
        ArrayList<Intent> allSticky = null;
        if (stickyIntents != null) {
        ArrayList<StickyBroadcast> allSticky = null;
        if (stickyBroadcasts != null) {
            final ContentResolver resolver = mContext.getContentResolver();
            // Look for any matching sticky broadcasts...
            for (int i = 0, N = stickyIntents.size(); i < N; i++) {
                Intent intent = stickyIntents.get(i);
            for (int i = 0, N = stickyBroadcasts.size(); i < N; i++) {
                final StickyBroadcast broadcast = stickyBroadcasts.get(i);
                Intent intent = broadcast.intent;
                // Don't provided intents that aren't available to instant apps.
                if (instantApp &&
                        (intent.getFlags() & Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS) == 0) {
@@ -13816,15 +13848,15 @@ public class ActivityManagerService extends IActivityManager.Stub
                // cannot lock ActivityManagerService here.
                if (filter.match(resolver, intent, true, TAG) >= 0) {
                    if (allSticky == null) {
                        allSticky = new ArrayList<Intent>();
                        allSticky = new ArrayList<>();
                    }
                    allSticky.add(intent);
                    allSticky.add(broadcast);
                }
            }
        }
        // The first sticky in the list is returned directly back to the client.
        Intent sticky = allSticky != null ? allSticky.get(0) : null;
        Intent sticky = allSticky != null ? allSticky.get(0).intent : null;
        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Register receiver " + filter + ": " + sticky);
        if (receiver == null) {
            return sticky;
@@ -13906,10 +13938,11 @@ public class ActivityManagerService extends IActivityManager.Stub
                final int stickyCount = allSticky.size();
                for (int i = 0; i < stickyCount; i++) {
                    Intent intent = allSticky.get(i);
                    BroadcastQueue queue = broadcastQueueForIntent(intent);
                    BroadcastRecord r = new BroadcastRecord(queue, intent, null,
                            null, null, -1, -1, false, null, null, null, null, OP_NONE, null,
                    final StickyBroadcast broadcast = allSticky.get(i);
                    BroadcastQueue queue = broadcastQueueForIntent(broadcast.intent);
                    BroadcastRecord r = new BroadcastRecord(queue, broadcast.intent, null,
                            null, null, -1, -1, false, null, null, null, null, OP_NONE,
                            BroadcastOptions.makeWithDeferUntilActive(broadcast.deferUntilActive),
                            receivers, null, null, 0, null, null, false, true, true, -1,
                            BackgroundStartPrivileges.NONE,
                            false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */,
@@ -14288,7 +14321,8 @@ public class ActivityManagerService extends IActivityManager.Stub
        if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
                (sticky ? "Broadcast sticky: ": "Broadcast: ") + intent
                + " ordered=" + ordered + " userid=" + userId);
                        + " ordered=" + ordered + " userid=" + userId
                        + " options=" + (brOptions == null ? "null" : brOptions.toBundle()));
        if ((resultTo != null) && !ordered) {
            if (!mEnableModernQueue) {
                Slog.w(TAG, "Broadcast " + intent + " not ordered but result callback requested!");
@@ -14753,15 +14787,15 @@ public class ActivityManagerService extends IActivityManager.Stub
                // But first, if this is not a broadcast to all users, then
                // make sure it doesn't conflict with an existing broadcast to
                // all users.
                ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(
                ArrayMap<String, ArrayList<StickyBroadcast>> stickies = mStickyBroadcasts.get(
                        UserHandle.USER_ALL);
                if (stickies != null) {
                    ArrayList<Intent> list = stickies.get(intent.getAction());
                    ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
                    if (list != null) {
                        int N = list.size();
                        int i;
                        for (i=0; i<N; i++) {
                            if (intent.filterEquals(list.get(i))) {
                            if (intent.filterEquals(list.get(i).intent)) {
                                throw new IllegalArgumentException(
                                        "Sticky broadcast " + intent + " for user "
                                        + userId + " conflicts with existing global broadcast");
@@ -14770,27 +14804,30 @@ public class ActivityManagerService extends IActivityManager.Stub
                    }
                }
            }
            ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(userId);
            ArrayMap<String, ArrayList<StickyBroadcast>> stickies = mStickyBroadcasts.get(userId);
            if (stickies == null) {
                stickies = new ArrayMap<>();
                mStickyBroadcasts.put(userId, stickies);
            }
            ArrayList<Intent> list = stickies.get(intent.getAction());
            ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
            if (list == null) {
                list = new ArrayList<>();
                stickies.put(intent.getAction(), list);
            }
            final boolean deferUntilActive = BroadcastRecord.calculateDeferUntilActive(
                    callingUid, brOptions, resultTo, ordered,
                    BroadcastRecord.calculateUrgent(intent, brOptions));
            final int stickiesCount = list.size();
            int i;
            for (i = 0; i < stickiesCount; i++) {
                if (intent.filterEquals(list.get(i))) {
                if (intent.filterEquals(list.get(i).intent)) {
                    // This sticky already exists, replace it.
                    list.set(i, new Intent(intent));
                    list.set(i, StickyBroadcast.create(new Intent(intent), deferUntilActive));
                    break;
                }
            }
            if (i >= stickiesCount) {
                list.add(new Intent(intent));
                list.add(StickyBroadcast.create(new Intent(intent), deferUntilActive));
            }
        }
@@ -14984,6 +15021,16 @@ public class ActivityManagerService extends IActivityManager.Stub
        return ActivityManager.BROADCAST_SUCCESS;
    }
    @VisibleForTesting
    ArrayList<StickyBroadcast> getStickyBroadcasts(String action, int userId) {
        final ArrayMap<String, ArrayList<StickyBroadcast>> stickyBroadcasts =
                mStickyBroadcasts.get(userId);
        if (stickyBroadcasts == null) {
            return null;
        }
        return stickyBroadcasts.get(action);
    }
    /**
     * @return uid from the extra field {@link Intent#EXTRA_UID} if present, Otherwise -1
     */
@@ -15165,14 +15212,14 @@ public class ActivityManagerService extends IActivityManager.Stub
                Slog.w(TAG, msg);
                throw new SecurityException(msg);
            }
            ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(userId);
            ArrayMap<String, ArrayList<StickyBroadcast>> stickies = mStickyBroadcasts.get(userId);
            if (stickies != null) {
                ArrayList<Intent> list = stickies.get(intent.getAction());
                ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
                if (list != null) {
                    int N = list.size();
                    int i;
                    for (i=0; i<N; i++) {
                        if (intent.filterEquals(list.get(i))) {
                        if (intent.filterEquals(list.get(i).intent)) {
                            list.remove(i);
                            break;
                        }
+2 −1
Original line number Diff line number Diff line
@@ -49,7 +49,8 @@ public class ActivityManagerInternalTest {
    private static final long TEST_PROC_STATE_SEQ1 = 1111;
    private static final long TEST_PROC_STATE_SEQ2 = 1112;

    @Rule public ServiceThreadRule mServiceThreadRule = new ServiceThreadRule();
    @Rule public final ApplicationExitInfoTest.ServiceThreadRule
            mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule();

    @Mock private ActivityManagerService.Injector mMockInjector;

+95 −3
Original line number Diff line number Diff line
@@ -48,8 +48,11 @@ import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -59,13 +62,16 @@ import static org.mockito.Mockito.when;

import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
import android.app.IApplicationThread;
import android.app.IUidObserver;
import android.app.SyncNotedAppOp;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManagerInternal;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -84,6 +90,7 @@ import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;

import com.android.server.LocalServices;
import com.android.server.am.ActivityManagerService.StickyBroadcast;
import com.android.server.am.ProcessList.IsolatedUidRange;
import com.android.server.am.ProcessList.IsolatedUidRangeAllocator;
import com.android.server.am.UidObserverController.ChangeRecord;
@@ -102,10 +109,12 @@ import org.mockito.MockitoAnnotations;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;

@@ -120,6 +129,15 @@ import java.util.function.Function;
public class ActivityManagerServiceTest {
    private static final String TAG = ActivityManagerServiceTest.class.getSimpleName();

    private static final int TEST_USER = 11;

    private static final String TEST_ACTION1 = "com.android.server.am.TEST_ACTION1";
    private static final String TEST_ACTION2 = "com.android.server.am.TEST_ACTION2";
    private static final String TEST_ACTION3 = "com.android.server.am.TEST_ACTION3";

    private static final String TEST_EXTRA_KEY1 = "com.android.server.am.TEST_EXTRA_KEY1";
    private static final String TEST_EXTRA_VALUE1 = "com.android.server.am.TEST_EXTRA_VALUE1";

    private static final int TEST_UID = 11111;
    private static final int USER_ID = 666;

@@ -150,11 +168,14 @@ public class ActivityManagerServiceTest {
        LocalServices.removeServiceForTest(PackageManagerInternal.class);
    }

    @Rule public ServiceThreadRule mServiceThreadRule = new ServiceThreadRule();
    @Rule
    public final ApplicationExitInfoTest.ServiceThreadRule
            mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule();

    private Context mContext = getInstrumentation().getTargetContext();

    @Mock private AppOpsService mAppOpsService;
    @Mock private UserController mUserController;

    private TestInjector mInjector;
    private ActivityManagerService mAms;
@@ -169,7 +190,14 @@ public class ActivityManagerServiceTest {
        mHandlerThread.start();
        mHandler = new TestHandler(mHandlerThread.getLooper());
        mInjector = new TestInjector(mContext);
        mAms = new ActivityManagerService(mInjector, mServiceThreadRule.getThread());
        doAnswer(invocation -> {
            final int userId = invocation.getArgument(2);
            return userId;
        }).when(mUserController).handleIncomingUser(anyInt(), anyInt(), anyInt(), anyBoolean(),
                anyInt(), any(), any());
        doReturn(true).when(mUserController).isUserOrItsParentRunning(anyInt());
        mAms = new ActivityManagerService(mInjector, mServiceThreadRule.getThread(),
                mUserController);
        mAms.mConstants.mNetworkAccessTimeoutMs = 2000;
        mAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
        mAms.mActivityTaskManager.initialize(null, null, mHandler.getLooper());
@@ -311,7 +339,8 @@ public class ActivityManagerServiceTest {
    }

    private void verifyUidRangesNoOverlap(IsolatedUidRange uidRange1, IsolatedUidRange uidRange2) {
        IsolatedUidRange lowRange = uidRange1.mFirstUid <= uidRange2.mFirstUid ? uidRange1 : uidRange2;
        IsolatedUidRange lowRange = uidRange1.mFirstUid <= uidRange2.mFirstUid
                ? uidRange1 : uidRange2;
        IsolatedUidRange highRange = lowRange == uidRange1  ? uidRange2 : uidRange1;

        assertTrue(highRange.mFirstUid > lowRange.mLastUid);
@@ -609,6 +638,69 @@ public class ActivityManagerServiceTest {
        }
    }

    @Test
    public void testBroadcastStickyIntent() {
        final Intent intent1 = new Intent(TEST_ACTION1);
        final Intent intent2 = new Intent(TEST_ACTION2)
                .putExtra(TEST_EXTRA_KEY1, TEST_EXTRA_VALUE1);
        final Intent intent3 = new Intent(TEST_ACTION3);
        final BroadcastOptions options = BroadcastOptions.makeWithDeferUntilActive(true);

        broadcastIntent(intent1, null, true);
        assertStickyBroadcasts(mAms.getStickyBroadcasts(TEST_ACTION1, TEST_USER),
                StickyBroadcast.create(intent1, false));
        assertNull(mAms.getStickyBroadcasts(TEST_ACTION2, TEST_USER));
        assertNull(mAms.getStickyBroadcasts(TEST_ACTION3, TEST_USER));

        broadcastIntent(intent2, options.toBundle(), true);
        assertStickyBroadcasts(mAms.getStickyBroadcasts(TEST_ACTION1, TEST_USER),
                StickyBroadcast.create(intent1, false));
        assertStickyBroadcasts(mAms.getStickyBroadcasts(TEST_ACTION2, TEST_USER),
                StickyBroadcast.create(intent2, true));
        assertNull(mAms.getStickyBroadcasts(TEST_ACTION3, TEST_USER));

        broadcastIntent(intent3, null, true);
        assertStickyBroadcasts(mAms.getStickyBroadcasts(TEST_ACTION1, TEST_USER),
                StickyBroadcast.create(intent1, false));
        assertStickyBroadcasts(mAms.getStickyBroadcasts(TEST_ACTION2, TEST_USER),
                StickyBroadcast.create(intent2, true));
        assertStickyBroadcasts(mAms.getStickyBroadcasts(TEST_ACTION3, TEST_USER),
                StickyBroadcast.create(intent3, false));
    }

    @SuppressWarnings("GuardedBy")
    private void broadcastIntent(Intent intent, Bundle options, boolean sticky) {
        final int res = mAms.broadcastIntentLocked(null, null, null, intent, null, null, 0,
                null, null, null, null, null, 0, options, false, sticky,
                Process.myPid(), Process.myUid(), Process.myUid(), Process.myPid(), TEST_USER);
        assertEquals(ActivityManager.BROADCAST_SUCCESS, res);
    }

    private void assertStickyBroadcasts(ArrayList<StickyBroadcast> actualBroadcasts,
            StickyBroadcast... expectedBroadcasts) {
        final String errMsg = "Expected: " + Arrays.toString(expectedBroadcasts)
                + "; Actual: " + Arrays.toString(actualBroadcasts.toArray());
        assertEquals(errMsg, expectedBroadcasts.length, actualBroadcasts.size());
        for (int i = 0; i < expectedBroadcasts.length; ++i) {
            final StickyBroadcast expected = expectedBroadcasts[i];
            final StickyBroadcast actual = actualBroadcasts.get(i);
            assertTrue(errMsg, areEquals(expected, actual));
        }
    }

    private boolean areEquals(StickyBroadcast a, StickyBroadcast b) {
        if (!Objects.equals(a.intent.getAction(), b.intent.getAction())) {
            return false;
        }
        if (!Bundle.kindofEquals(a.intent.getExtras(), b.intent.getExtras())) {
            return false;
        }
        if (a.deferUntilActive != b.deferUntilActive) {
            return false;
        }
        return true;
    }

    private interface ObserverChangesVerifier {
        void verify(IUidObserver observer, ChangeRecord changeItem) throws RemoteException;
    }