Revert "Revert "Handle package broadcasts before apps do""

This reverts commit efbbe7ea9db8adab6bff407db1d7fc54bbaf31ee.

Change-Id: I3dbb080db5121307f533a7d411f4f5d47225500e
diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java
index 3f8bad1..de52f73 100644
--- a/core/java/android/content/pm/ShortcutServiceInternal.java
+++ b/core/java/android/content/pm/ShortcutServiceInternal.java
@@ -73,4 +73,11 @@
      * any locks in this method.
      */
     public abstract void onSystemLocaleChangedNoLock();
+
+    /**
+     * Called by PM before sending package broadcasts to other components.  PM doesn't hold the PM
+     * lock, but do not take any locks in here anyway, and don't do any heavy tasks, as doing so
+     * would slow down all the package broadcasts.
+     */
+    public abstract void onPackageBroadcast(Intent intent);
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8d002bd..455e1fa 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -159,6 +159,7 @@
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.pm.ShortcutServiceInternal;
 import android.content.pm.Signature;
 import android.content.pm.UserInfo;
 import android.content.pm.VerifierDeviceIdentity;
@@ -11438,6 +11439,9 @@
                     } else {
                         resolvedUserIds = userIds;
                     }
+                    final ShortcutServiceInternal shortcutService =
+                            LocalServices.getService(ShortcutServiceInternal.class);
+
                     for (int id : resolvedUserIds) {
                         final Intent intent = new Intent(action,
                                 pkg != null ? Uri.fromParts("package", pkg, null) : null);
@@ -11462,6 +11466,10 @@
                                     + intent.toShortString(false, true, false, false)
                                     + " " + intent.getExtras(), here);
                         }
+                        // TODO b/29385425 Consider making lifecycle callbacks for this.
+                        if (shortcutService != null) {
+                            shortcutService.onPackageBroadcast(intent);
+                        }
                         am.broadcastIntent(null, intent, null, finishedReceiver,
                                 0, null, null, null, android.app.AppOpsManager.OP_NONE,
                                 null, finishedReceiver != null, false, id);
diff --git a/services/core/java/com/android/server/pm/ShortcutPendingTasks.java b/services/core/java/com/android/server/pm/ShortcutPendingTasks.java
new file mode 100644
index 0000000..a5ace56
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ShortcutPendingTasks.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2016 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.pm;
+
+import android.annotation.NonNull;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.BooleanSupplier;
+import java.util.function.Consumer;
+import java.util.logging.Handler;
+
+/**
+ * Used by {@link ShortcutService} to register tasks to be executed on Handler and also wait for
+ * all pending tasks.
+ *
+ * Tasks can be registered with {@link #addTask(Runnable)}.  Call {@link #waitOnAllTasks()} to wait
+ * on all tasks that have been registered.
+ *
+ * In order to avoid deadlocks, {@link #waitOnAllTasks} MUST NOT be called with any lock held, nor
+ * on the handler thread.  These conditions are checked by {@link #mWaitThreadChecker} and wtf'ed.
+ *
+ * During unit tests, we can't run tasks asynchronously, so we just run Runnables synchronously,
+ * which also means the "is lock held" check doesn't work properly during unit tests (e.g. normally
+ * when a Runnable is executed on a Handler, the thread doesn't hold any lock, but during the tests
+ * we just run a Runnable on the thread that registers it, so the thread may or may not hold locks.)
+ * So unfortunately we have to disable {@link #mWaitThreadChecker} during unit tests.
+ *
+ * Because of the complications like those, this class should be used only for specific purposes:
+ * - {@link #addTask(Runnable)} should only be used to register tasks on callbacks from lower level
+ * services like the package manager or the activity manager.
+ *
+ * - {@link #waitOnAllTasks} should only be called at the entry point of RPC calls (or the test only
+ * accessors}.
+ */
+public class ShortcutPendingTasks {
+    private static final String TAG = "ShortcutPendingTasks";
+
+    private static final boolean DEBUG = false || ShortcutService.DEBUG; // DO NOT SUBMIT WITH TRUE.
+
+    private final Consumer<Runnable> mRunner;
+
+    private final BooleanSupplier mWaitThreadChecker;
+
+    private final Consumer<Throwable> mExceptionHandler;
+
+    /** # of tasks in the queue, including the running one. */
+    private final AtomicInteger mRunningTaskCount = new AtomicInteger();
+
+    /** For dumpsys */
+    private final AtomicLong mLastTaskStartTime = new AtomicLong();
+
+    /**
+     * Constructor.  In order to allow injection during unit tests, it doesn't take a
+     * {@link Handler} directly, and instead takes {@code runner} which will post an argument
+     * to a handler.
+     */
+    public ShortcutPendingTasks(Consumer<Runnable> runner, BooleanSupplier waitThreadChecker,
+            Consumer<Throwable> exceptionHandler) {
+        mRunner = runner;
+        mWaitThreadChecker = waitThreadChecker;
+        mExceptionHandler = exceptionHandler;
+    }
+
+    private static void dlog(String message) {
+        if (DEBUG) {
+            Slog.d(TAG, message);
+        }
+    }
+
+    /**
+     * Block until all tasks that are already queued finish.  DO NOT call it while holding any lock
+     * or on the handler thread.
+     */
+    public boolean waitOnAllTasks() {
+        dlog("waitOnAllTasks: enter");
+        try {
+            // Make sure it's not holding the lock.
+            if (!mWaitThreadChecker.getAsBoolean()) {
+                return false;
+            }
+
+            // Optimize for the no-task case.
+            if (mRunningTaskCount.get() == 0) {
+                return true;
+            }
+
+            final CountDownLatch latch = new CountDownLatch(1);
+
+            addTask(latch::countDown);
+
+            for (; ; ) {
+                try {
+                    if (latch.await(1, TimeUnit.SECONDS)) {
+                        return true;
+                    }
+                    dlog("waitOnAllTasks: Task(s) still running...");
+                } catch (InterruptedException ignore) {
+                }
+            }
+        } finally {
+            dlog("waitOnAllTasks: exit");
+        }
+    }
+
+    /**
+     * Add a new task.  This operation is lock-free.
+     */
+    public void addTask(Runnable task) {
+        mRunningTaskCount.incrementAndGet();
+        mLastTaskStartTime.set(System.currentTimeMillis());
+
+        dlog("Task registered");
+
+        mRunner.accept(() -> {
+            try {
+                dlog("Task started");
+
+                task.run();
+            } catch (Throwable th) {
+                mExceptionHandler.accept(th);
+            } finally {
+                dlog("Task finished");
+                mRunningTaskCount.decrementAndGet();
+            }
+        });
+    }
+
+    public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+        pw.print(prefix);
+        pw.print("Pending tasks:  # running tasks: ");
+        pw.println(mRunningTaskCount.get());
+
+        pw.print(prefix);
+        pw.print("  Last task started time: ");
+        final long lastStarted = mLastTaskStartTime.get();
+        pw.print(" [");
+        pw.print(lastStarted);
+        pw.print("] ");
+        pw.println(ShortcutService.formatTime(lastStarted));
+    }
+}
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 5f8cbbf..a9018b3 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -49,6 +49,7 @@
 import android.graphics.Canvas;
 import android.graphics.RectF;
 import android.graphics.drawable.Icon;
+import android.net.Uri;
 import android.os.Binder;
 import android.os.Environment;
 import android.os.FileUtils;
@@ -81,7 +82,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.content.PackageMonitor;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.Preconditions;
@@ -122,9 +122,6 @@
 
 /**
  * TODO:
- * - Deal with the async nature of PACKAGE_ADD.  Basically when a publisher does anything after
- *   it's upgraded, the manager should make sure the upgrade process has been executed.
- *
  * - getIconMaxWidth()/getIconMaxHeight() should use xdpi and ydpi.
  *   -> But TypedValue.applyDimension() doesn't differentiate x and y..?
  *
@@ -304,6 +301,8 @@
 
     private final AtomicBoolean mBootCompleted = new AtomicBoolean();
 
+    private final ShortcutPendingTasks mPendingTasks;
+
     private static final int PACKAGE_MATCH_FLAGS =
             PackageManager.MATCH_DIRECT_BOOT_AWARE
                     | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
@@ -377,16 +376,41 @@
         mUsageStatsManagerInternal = Preconditions.checkNotNull(
                 LocalServices.getService(UsageStatsManagerInternal.class));
 
+        mPendingTasks = new ShortcutPendingTasks(
+                this::injectPostToHandler,
+                this::injectCheckPendingTaskWaitThread,
+                throwable -> wtf(throwable.getMessage(), throwable));
+
         if (onlyForPackageManagerApis) {
             return; // Don't do anything further.  For unit tests only.
         }
 
-        mPackageMonitor.register(context, looper, UserHandle.ALL, /* externalStorage= */ false);
-
         injectRegisterUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_PROCSTATE
                 | ActivityManager.UID_OBSERVER_GONE);
     }
 
+    /**
+     * Check whether {@link ShortcutPendingTasks#waitOnAllTasks()} can be called on the current
+     * thread.
+     *
+     * During unit tests, all tasks are executed synchronously which makes the lock held check would
+     * misfire, so we override this method to always return true.
+     */
+    @VisibleForTesting
+    boolean injectCheckPendingTaskWaitThread() {
+        // We shouldn't wait while holding mLock.  We should never do this so wtf().
+        if (Thread.holdsLock(mLock)) {
+            wtf("waitOnAllTasks() called while holding the lock");
+            return false;
+        }
+        // This shouldn't be called on the handler thread either.
+        if (Thread.currentThread() == mHandler.getLooper().getThread()) {
+            wtf("waitOnAllTasks() called on handler thread");
+            return false;
+        }
+        return true;
+    }
+
     void logDurationStat(int statId, long start) {
         synchronized (mStatLock) {
             mCountStats[statId]++;
@@ -1492,6 +1516,8 @@
             @UserIdInt int userId) {
         verifyCaller(packageName, userId);
 
+        mPendingTasks.waitOnAllTasks();
+
         final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
         final int size = newShortcuts.size();
 
@@ -1541,6 +1567,8 @@
             @UserIdInt int userId) {
         verifyCaller(packageName, userId);
 
+        mPendingTasks.waitOnAllTasks();
+
         final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
         final int size = newShortcuts.size();
 
@@ -1619,6 +1647,8 @@
             @UserIdInt int userId) {
         verifyCaller(packageName, userId);
 
+        mPendingTasks.waitOnAllTasks();
+
         final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
         final int size = newShortcuts.size();
 
@@ -1669,6 +1699,8 @@
         verifyCaller(packageName, userId);
         Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
 
+        mPendingTasks.waitOnAllTasks();
+
         synchronized (mLock) {
             final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
 
@@ -1696,6 +1728,8 @@
         verifyCaller(packageName, userId);
         Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
 
+        mPendingTasks.waitOnAllTasks();
+
         synchronized (mLock) {
             final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
 
@@ -1716,6 +1750,8 @@
         verifyCaller(packageName, userId);
         Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
 
+        mPendingTasks.waitOnAllTasks();
+
         synchronized (mLock) {
             final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
 
@@ -1738,6 +1774,8 @@
     public void removeAllDynamicShortcuts(String packageName, @UserIdInt int userId) {
         verifyCaller(packageName, userId);
 
+        mPendingTasks.waitOnAllTasks();
+
         synchronized (mLock) {
             getPackageShortcutsLocked(packageName, userId).deleteAllDynamicShortcuts();
         }
@@ -1750,6 +1788,9 @@
     public ParceledListSlice<ShortcutInfo> getDynamicShortcuts(String packageName,
             @UserIdInt int userId) {
         verifyCaller(packageName, userId);
+
+        mPendingTasks.waitOnAllTasks();
+
         synchronized (mLock) {
             return getShortcutsWithQueryLocked(
                     packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
@@ -1761,6 +1802,9 @@
     public ParceledListSlice<ShortcutInfo> getManifestShortcuts(String packageName,
             @UserIdInt int userId) {
         verifyCaller(packageName, userId);
+
+        mPendingTasks.waitOnAllTasks();
+
         synchronized (mLock) {
             return getShortcutsWithQueryLocked(
                     packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
@@ -1772,6 +1816,9 @@
     public ParceledListSlice<ShortcutInfo> getPinnedShortcuts(String packageName,
             @UserIdInt int userId) {
         verifyCaller(packageName, userId);
+
+        mPendingTasks.waitOnAllTasks();
+
         synchronized (mLock) {
             return getShortcutsWithQueryLocked(
                     packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
@@ -1801,6 +1848,8 @@
     public int getRemainingCallCount(String packageName, @UserIdInt int userId) {
         verifyCaller(packageName, userId);
 
+        mPendingTasks.waitOnAllTasks();
+
         synchronized (mLock) {
             return mMaxUpdatesPerInterval
                     - getPackageShortcutsLocked(packageName, userId).getApiCallCount();
@@ -1811,6 +1860,8 @@
     public long getRateLimitResetTime(String packageName, @UserIdInt int userId) {
         verifyCaller(packageName, userId);
 
+        mPendingTasks.waitOnAllTasks();
+
         synchronized (mLock) {
             return getNextResetTimeLocked();
         }
@@ -1829,6 +1880,8 @@
     public void reportShortcutUsed(String packageName, String shortcutId, int userId) {
         verifyCaller(packageName, userId);
 
+        mPendingTasks.waitOnAllTasks();
+
         Preconditions.checkNotNull(shortcutId);
 
         if (DEBUG) {
@@ -1861,6 +1914,8 @@
     public void resetThrottling() {
         enforceSystemOrShell();
 
+        mPendingTasks.waitOnAllTasks();
+
         resetThrottlingInner(getCallingUserId());
     }
 
@@ -1893,6 +1948,9 @@
         if (DEBUG) {
             Slog.d(TAG, "onApplicationActive: package=" + packageName + "  userid=" + userId);
         }
+
+        mPendingTasks.waitOnAllTasks();
+
         enforceResetThrottlingPermission();
         resetPackageThrottling(packageName, userId);
     }
@@ -2055,6 +2113,14 @@
                 @Nullable String packageName, @Nullable List<String> shortcutIds,
                 @Nullable ComponentName componentName,
                 int queryFlags, int userId) {
+
+            // When this method is called from onShortcutChangedInner() in LauncherApps,
+            // we're on the handler thread.  Do not try to wait on tasks.  Not waiting for pending
+            // tasks on this specific case should be fine.
+            if (Thread.currentThread() != mHandler.getLooper().getThread()) {
+                mPendingTasks.waitOnAllTasks();
+            }
+
             final ArrayList<ShortcutInfo> ret = new ArrayList<>();
             final boolean cloneKeyFieldOnly =
                     ((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) != 0);
@@ -2133,6 +2199,8 @@
             Preconditions.checkStringNotEmpty(packageName, "packageName");
             Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
 
+            mPendingTasks.waitOnAllTasks();
+
             synchronized (mLock) {
                 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
                         .attemptToRestoreIfNeededAndSave();
@@ -2170,6 +2238,8 @@
             Preconditions.checkStringNotEmpty(packageName, "packageName");
             Preconditions.checkNotNull(shortcutIds, "shortcutIds");
 
+            mPendingTasks.waitOnAllTasks();
+
             synchronized (mLock) {
                 final ShortcutLauncher launcher =
                         getLauncherShortcutsLocked(callingPackage, userId, launcherUserId);
@@ -2190,6 +2260,8 @@
             Preconditions.checkStringNotEmpty(packageName, "packageName can't be empty");
             Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty");
 
+            mPendingTasks.waitOnAllTasks();
+
             synchronized (mLock) {
                 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
                         .attemptToRestoreIfNeededAndSave();
@@ -2220,6 +2292,8 @@
             Preconditions.checkNotNull(packageName, "packageName");
             Preconditions.checkNotNull(shortcutId, "shortcutId");
 
+            mPendingTasks.waitOnAllTasks();
+
             synchronized (mLock) {
                 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
                         .attemptToRestoreIfNeededAndSave();
@@ -2244,6 +2318,8 @@
             Preconditions.checkNotNull(packageName, "packageName");
             Preconditions.checkNotNull(shortcutId, "shortcutId");
 
+            mPendingTasks.waitOnAllTasks();
+
             synchronized (mLock) {
                 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
                         .attemptToRestoreIfNeededAndSave();
@@ -2304,9 +2380,18 @@
                 if (DEBUG) {
                     Slog.d(TAG, "onSystemLocaleChangedNoLock: " + mLocaleChangeSequenceNumber.get());
                 }
-                injectPostToHandler(() -> handleLocaleChanged());
+                mPendingTasks.addTask(() -> handleLocaleChanged());
             }
         }
+
+        @Override
+        public void onPackageBroadcast(Intent intent) {
+            if (DEBUG) {
+                Slog.d(TAG, "onPackageBroadcast");
+            }
+            mPendingTasks.addTask(() -> ShortcutService.this.onPackageBroadcast(
+                    new Intent(intent)));
+        }
     }
 
     void handleLocaleChanged() {
@@ -2323,58 +2408,49 @@
         }
     }
 
-    /**
-     * Package event callbacks.
-     */
-    @VisibleForTesting
-    final PackageMonitor mPackageMonitor = new PackageMonitor() {
-
-        private boolean isUserUnlocked() {
-            return mUserManager.isUserUnlocked(getChangingUserId());
+    private void onPackageBroadcast(Intent intent) {
+        final int userId  = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+        if (userId == UserHandle.USER_NULL) {
+            Slog.w(TAG, "Intent broadcast does not contain user handle: " + intent);
+            return;
         }
 
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            // clearCallingIdentity is not needed normally, but need to do it for the unit test.
-            final long token = injectClearCallingIdentity();
-            try {
-                super.onReceive(context, intent);
-            } finally {
-                injectRestoreCallingIdentity(token);
+        final String action = intent.getAction();
+
+        if (!mUserManager.isUserUnlocked(userId)) {
+            if (DEBUG) {
+                Slog.d(TAG, "Ignoring package broadcast " + action + " for locked/stopped user "
+                        + userId);
             }
+            return;
         }
 
-        @Override
-        public void onPackageAdded(String packageName, int uid) {
-            if (!isUserUnlocked()) return;
-            handlePackageAdded(packageName, getChangingUserId());
+        final Uri intentUri = intent.getData();
+        final String packageName = (intentUri != null) ? intentUri.getSchemeSpecificPart() : null;
+        if (packageName == null) {
+            Slog.w(TAG, "Intent broadcast does not contain package name: " + intent);
+            return;
         }
 
-        @Override
-        public void onPackageUpdateFinished(String packageName, int uid) {
-            if (!isUserUnlocked()) return;
-            handlePackageUpdateFinished(packageName, getChangingUserId());
-        }
+        final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
 
-        @Override
-        public void onPackageRemoved(String packageName, int uid) {
-            if (!isUserUnlocked()) return;
-            handlePackageRemoved(packageName, getChangingUserId());
-        }
+        if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
+            if (replacing) {
+                handlePackageUpdateFinished(packageName, userId);
+            } else {
+                handlePackageAdded(packageName, userId);
+            }
+        } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+            if (!replacing) {
+                handlePackageRemoved(packageName, userId);
+            }
+        } else if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
+            handlePackageChanged(packageName, userId);
 
-        @Override
-        public void onPackageDataCleared(String packageName, int uid) {
-            if (!isUserUnlocked()) return;
-            handlePackageDataCleared(packageName, getChangingUserId());
+        } else if (Intent.ACTION_PACKAGE_DATA_CLEARED.equals(action)) {
+            handlePackageDataCleared(packageName, userId);
         }
-
-        @Override
-        public boolean onPackageChanged(String packageName, int uid, String[] components) {
-            if (!isUserUnlocked()) return false;
-            handlePackageChanged(packageName, getChangingUserId());
-            return false; // We don't need to receive onSomePackagesChanged(), so just false.
-        }
-    };
+    }
 
     /**
      * Called when a user is unlocked.
@@ -3021,6 +3097,9 @@
                 pw.println(Log.getStackTraceString(mLastWtfStacktrace));
             }
 
+            pw.println();
+            mPendingTasks.dump(pw, "  ");
+
             for (int i = 0; i < mUsers.size(); i++) {
                 pw.println();
                 mUsers.valueAt(i).dump(pw, "  ");
@@ -3069,6 +3148,8 @@
 
         enforceShell();
 
+        mPendingTasks.waitOnAllTasks();
+
         final int status = (new MyShellCommand()).exec(this, in, out, err, args, resultReceiver);
 
         resultReceiver.send(status, null);
@@ -3095,10 +3176,6 @@
                     case "--user":
                         if (takeUser) {
                             mUserId = UserHandle.parseUserArg(getNextArgRequired());
-                            if (!mUserManager.isUserUnlocked(mUserId)) {
-                                throw new CommandException(
-                                        "User " + mUserId + " is not running or locked");
-                            }
                             break;
                         }
                         // fallthrough
@@ -3424,6 +3501,7 @@
 
     @VisibleForTesting
     ShortcutPackage getPackageShortcutForTest(String packageName, int userId) {
+        mPendingTasks.waitOnAllTasks();
         synchronized (mLock) {
             final ShortcutUser user = mUsers.get(userId);
             if (user == null) return null;
@@ -3434,8 +3512,12 @@
 
     @VisibleForTesting
     ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) {
+        mPendingTasks.waitOnAllTasks();
         synchronized (mLock) {
-            final ShortcutPackage pkg = getPackageShortcutForTest(packageName, userId);
+            final ShortcutUser user = mUsers.get(userId);
+            if (user == null) return null;
+
+            final ShortcutPackage pkg = user.getAllPackagesForTest().get(packageName);
             if (pkg == null) return null;
 
             return pkg.findShortcutById(shortcutId);
@@ -3470,4 +3552,12 @@
             forEachLoadedUserLocked(u -> u.forAllPackageItems(ShortcutPackageItem::verifyStates));
         }
     }
+
+    ShortcutPendingTasks getPendingTasksForTest() {
+        return mPendingTasks;
+    }
+
+    Object getLockForTest() {
+        return mLock;
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index c0e604a..3d68b59 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -404,6 +404,11 @@
             // During tests, WTF is fatal.
             fail(message + "  exception: " + th + "\n" + Log.getStackTraceString(th));
         }
+
+        @Override
+        boolean injectCheckPendingTaskWaitThread() {
+            return true;
+        }
     }
 
     /** ShortcutManager with injection override methods. */
@@ -848,6 +853,8 @@
 
     protected void shutdownServices() {
         if (mService != null) {
+            mService.getPendingTasksForTest().waitOnAllTasks();
+
             // Flush all the unsaved data from the previous instance.
             mService.saveDirtyInfo();
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index fbee1a6..aa1072e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -62,6 +62,7 @@
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.Manifest.permission;
 import android.app.ActivityManager;
@@ -1297,8 +1298,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_3);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-        mService.mPackageMonitor.onReceive(getTestContext(),
-                genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+        mInternal.onPackageBroadcast(genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
             assertTrue(mManager.setDynamicShortcuts(list(
@@ -1316,8 +1316,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_2);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-        mService.mPackageMonitor.onReceive(getTestContext(),
-                genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+        mInternal.onPackageBroadcast(genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
             assertTrue(mManager.setDynamicShortcuts(list(
@@ -2815,7 +2814,7 @@
                     new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                     R.xml.shortcut_2);
             updatePackageVersion(CALLING_PACKAGE_1, 1);
-            mService.mPackageMonitor.onReceive(getTestContext(),
+            mInternal.onPackageBroadcast(
                     genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
         }).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
                 .areAllManifest()
@@ -2852,7 +2851,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_0);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-        mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         assertForLauncherCallback(mLauncherApps, () -> {
@@ -3472,7 +3471,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_2);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -3489,7 +3488,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_1);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -3851,7 +3850,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_1);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
             assertWith(getCallerShortcuts())
@@ -3891,7 +3890,7 @@
         assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_10));
 
         uninstallPackage(USER_0, CALLING_PACKAGE_1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageDeleteIntent(CALLING_PACKAGE_1, USER_0));
 
         assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
@@ -3911,7 +3910,7 @@
         mRunningUsers.put(USER_10, true);
 
         uninstallPackage(USER_10, CALLING_PACKAGE_2);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageDeleteIntent(CALLING_PACKAGE_2, USER_10));
 
         assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
@@ -4002,7 +4001,7 @@
         assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_10));
         assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_10));
 
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageDataClear(CALLING_PACKAGE_1, USER_0));
 
         assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
@@ -4021,7 +4020,7 @@
 
         mRunningUsers.put(USER_10, true);
 
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageDataClear(CALLING_PACKAGE_2, USER_10));
 
         assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
@@ -4048,7 +4047,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_2);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_10));
 
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
@@ -4069,7 +4068,7 @@
         });
 
         // Clear data
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageDataClear(CALLING_PACKAGE_1, USER_10));
 
         // Only manifest shortcuts will remain, and are no longer pinned.
@@ -4134,9 +4133,9 @@
         reset(c0);
         reset(c10);
 
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageUpdateIntent(CALLING_PACKAGE_1, USER_0));
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageUpdateIntent(CALLING_PACKAGE_1, USER_10));
 
         waitOnMainThread();
@@ -4157,7 +4156,7 @@
         updatePackageVersion(CALLING_PACKAGE_1, 1);
 
         // Then send the broadcast, to only user-0.
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageUpdateIntent(CALLING_PACKAGE_1, USER_0));
 
         waitOnMainThread();
@@ -4222,7 +4221,7 @@
         updatePackageVersion(CALLING_PACKAGE_2, 10);
 
         // Then send the broadcast, to only user-0.
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageUpdateIntent(CALLING_PACKAGE_2, USER_0));
         mService.handleUnlockUser(USER_10);
 
@@ -4246,7 +4245,7 @@
         updatePackageVersion(CALLING_PACKAGE_3, 100);
 
         // Then send the broadcast, to only user-0.
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageUpdateIntent(CALLING_PACKAGE_3, USER_0));
         mService.handleUnlockUser(USER_10);
 
@@ -4328,7 +4327,7 @@
 
         // Update the package.
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageUpdateIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -4357,7 +4356,7 @@
         mRunningUsers.put(USER_10, true);
 
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_10));
 
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
@@ -4389,7 +4388,7 @@
         });
 
         // First, no changes.
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageChangedIntent(CALLING_PACKAGE_1, USER_10));
 
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
@@ -4412,7 +4411,7 @@
 
         // Disable activity 1
         mEnabledActivityChecker = (activity, userId) -> !ACTIVITY1.equals(activity);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageChangedIntent(CALLING_PACKAGE_1, USER_10));
 
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
@@ -4432,7 +4431,7 @@
         // Re-enable activity 1.
         // Manifest shortcuts will be re-published, but dynamic ones are not.
         mEnabledActivityChecker = (activity, userId) -> true;
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageChangedIntent(CALLING_PACKAGE_1, USER_10));
 
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
@@ -4456,7 +4455,7 @@
         // Disable activity 2
         // Because "ms1-alt" and "s2" are both pinned, they will remain, but disabled.
         mEnabledActivityChecker = (activity, userId) -> !ACTIVITY2.equals(activity);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageChangedIntent(CALLING_PACKAGE_1, USER_10));
 
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
@@ -4519,7 +4518,7 @@
         setCaller(LAUNCHER_1, USER_0);
         assertForLauncherCallback(mLauncherApps, () -> {
             updatePackageVersion(CALLING_PACKAGE_1, 1);
-                    mService.mPackageMonitor.onReceive(getTestContext(),
+            mInternal.onPackageBroadcast(
                     genPackageUpdateIntent(CALLING_PACKAGE_1, USER_0));
         }).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
                 // Make sure the launcher gets callbacks.
@@ -5519,7 +5518,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_1);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -5540,7 +5539,7 @@
                 new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()),
                 R.xml.shortcut_5);
         updatePackageVersion(CALLING_PACKAGE_2, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_2, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -5577,7 +5576,7 @@
                 new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()),
                 R.xml.shortcut_2);
         updatePackageLastUpdateTime(CALLING_PACKAGE_2, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_2, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -5614,7 +5613,7 @@
         mRunningUsers.put(USER_10, false);
         mUnlockedUsers.put(USER_10, false);
 
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_2, USER_10));
         runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
             assertEmpty(mManager.getManifestShortcuts());
@@ -5624,7 +5623,7 @@
         // Try again, but the user is locked, so still ignored.
         mRunningUsers.put(USER_10, true);
 
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_2, USER_10));
         runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
             assertEmpty(mManager.getManifestShortcuts());
@@ -5635,7 +5634,7 @@
         mUnlockedUsers.put(USER_10, true);
 
         // Send PACKAGE_ADD broadcast to have Package 2 on user-10 publish manifest shortcuts.
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_2, USER_10));
 
         runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
@@ -5676,7 +5675,7 @@
                 R.xml.shortcut_5_reverse);
 
         updatePackageLastUpdateTime(CALLING_PACKAGE_2, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_2, USER_0));
 
         runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
@@ -5704,7 +5703,7 @@
                 new ComponentName(CALLING_PACKAGE_2, ShortcutActivity2.class.getName()),
                 R.xml.shortcut_0);
         updatePackageLastUpdateTime(CALLING_PACKAGE_2, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_2, USER_0));
 
         // No manifest shortcuts, and pinned ones are disabled.
@@ -5735,7 +5734,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_error_1);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         // Only the valid one is published.
@@ -5750,7 +5749,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_error_2);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         // Only the valid one is published.
@@ -5765,7 +5764,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_error_3);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         // Only the valid one is published.
@@ -5781,7 +5780,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_error_4);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -5809,7 +5808,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_5);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -5847,7 +5846,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_error_4);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         // Make sure 3, 4 and 5 still exist but disabled.
@@ -5895,7 +5894,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_5);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         // Only the valid one is published.
@@ -6000,7 +5999,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_2);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -6097,7 +6096,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_1);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         // Only the valid one is published.
@@ -6116,7 +6115,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_1_disable);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         // Because shortcut 1 wasn't pinned, it'll just go away.
@@ -6137,7 +6136,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_1);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         // Only the valid one is published.
@@ -6160,7 +6159,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_1_disable);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         // Because shortcut 1 was pinned, it'll still exist as pinned, but disabled.
@@ -6193,7 +6192,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_2_duplicate);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -6223,7 +6222,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity2.class.getName()),
                 R.xml.shortcut_5);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -6295,7 +6294,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_5);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -6345,7 +6344,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_2);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(LAUNCHER_1, USER_0, () -> {
@@ -6356,7 +6355,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_1);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -6438,7 +6437,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_5);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -6508,7 +6507,7 @@
                     new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                     R.xml.shortcut_2);
             updatePackageVersion(CALLING_PACKAGE_1, 1);
-                    mService.mPackageMonitor.onReceive(getTestContext(),
+            mInternal.onPackageBroadcast(
                     genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
             assertEquals(2, mManager.getManifestShortcuts().size());
 
@@ -6634,7 +6633,7 @@
                     new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                     R.xml.shortcut_2);
             updatePackageVersion(CALLING_PACKAGE_1, 1);
-                    mService.mPackageMonitor.onReceive(getTestContext(),
+            mInternal.onPackageBroadcast(
                     genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
             assertEquals(2, mManager.getManifestShortcuts().size());
@@ -6783,7 +6782,7 @@
                     new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                     R.xml.shortcut_1);
             updatePackageVersion(CALLING_PACKAGE_1, 1);
-                    mService.mPackageMonitor.onReceive(getTestContext(),
+            mInternal.onPackageBroadcast(
                     genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
             assertEquals(1, mManager.getManifestShortcuts().size());
 
@@ -6803,7 +6802,7 @@
                     new ComponentName(CALLING_PACKAGE_1, ShortcutActivity2.class.getName()),
                     R.xml.shortcut_1_alt);
             updatePackageVersion(CALLING_PACKAGE_1, 1);
-                    mService.mPackageMonitor.onReceive(getTestContext(),
+            mInternal.onPackageBroadcast(
                     genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
             assertEquals(3, mManager.getManifestShortcuts().size());
 
@@ -6823,7 +6822,7 @@
                     new ComponentName(CALLING_PACKAGE_1, ShortcutActivity2.class.getName()),
                     R.xml.shortcut_5_alt); // manifest has 5, but max is 3, so a2 will have 3.
             updatePackageVersion(CALLING_PACKAGE_1, 1);
-                    mService.mPackageMonitor.onReceive(getTestContext(),
+            mInternal.onPackageBroadcast(
                     genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
             assertEquals(5, mManager.getManifestShortcuts().size());
 
@@ -6842,7 +6841,7 @@
                     new ComponentName(CALLING_PACKAGE_1, ShortcutActivity2.class.getName()),
                     R.xml.shortcut_0);
             updatePackageVersion(CALLING_PACKAGE_1, 1);
-                    mService.mPackageMonitor.onReceive(getTestContext(),
+            mInternal.onPackageBroadcast(
                     genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
             assertEquals(0, mManager.getManifestShortcuts().size());
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java
index eb4db7a..fcf7ea2 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java
@@ -66,7 +66,7 @@
     private void publishManifestShortcuts(ComponentName activity, int resId) {
         addManifestShortcutResource(activity, resId);
         updatePackageVersion(CALLING_PACKAGE, 1);
-        mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE, USER_0));
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutPendingTasksTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutPendingTasksTest.java
new file mode 100644
index 0000000..bf1ed98
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutPendingTasksTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2016 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.pm;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.test.MoreAsserts;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Run with:
+ adb install \
+   -r -g ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
+ adb shell am instrument -e class com.android.server.pm.ShortcutPendingTasksTest \
+   -w com.android.frameworks.servicestests
+ */
+@LargeTest
+public class ShortcutPendingTasksTest extends BaseShortcutManagerTest {
+    public void testAll() {
+        final AtomicReference<Throwable> thrown = new AtomicReference<>();
+
+        final AtomicBoolean threadCheckerResult = new AtomicBoolean(true);
+
+        final Handler handler = new Handler(Looper.getMainLooper());
+
+        final ShortcutPendingTasks tasks = new ShortcutPendingTasks(
+                handler::post,
+                threadCheckerResult::get,
+                thrown::set);
+
+        // No pending tasks, shouldn't block.
+        assertTrue(tasks.waitOnAllTasks());
+
+        final AtomicInteger counter = new AtomicInteger();
+
+        // Run one task.
+        tasks.addTask(() -> {
+            try {
+                Thread.sleep(1000);
+            } catch (InterruptedException ignore) {
+            }
+            counter.incrementAndGet();
+        });
+
+        assertTrue(tasks.waitOnAllTasks());
+        assertNull(thrown.get());
+
+        assertEquals(1, counter.get());
+
+        // Run 3 tasks.
+
+        // We use this ID to make sure only one task can run at the same time.
+        final AtomicInteger currentTaskId = new AtomicInteger();
+
+        tasks.addTask(() -> {
+            currentTaskId.set(1);
+            try {
+                Thread.sleep(500);
+            } catch (InterruptedException ignore) {
+            }
+            counter.incrementAndGet();
+            assertEquals(1, currentTaskId.get());
+        });
+        tasks.addTask(() -> {
+            currentTaskId.set(2);
+            try {
+                Thread.sleep(500);
+            } catch (InterruptedException ignore) {
+            }
+            counter.incrementAndGet();
+            assertEquals(2, currentTaskId.get());
+        });
+        tasks.addTask(() -> {
+            currentTaskId.set(3);
+            try {
+                Thread.sleep(500);
+            } catch (InterruptedException ignore) {
+            }
+            counter.incrementAndGet();
+            assertEquals(3, currentTaskId.get());
+        });
+
+        assertTrue(tasks.waitOnAllTasks());
+        assertNull(thrown.get());
+        assertEquals(4, counter.get());
+
+        // No tasks running, shouldn't block.
+        assertTrue(tasks.waitOnAllTasks());
+        assertNull(thrown.get());
+        assertEquals(4, counter.get());
+
+        // Now the thread checker returns false, so waitOnAllTasks() returns false.
+        threadCheckerResult.set(false);
+        assertFalse(tasks.waitOnAllTasks());
+
+        threadCheckerResult.set(true);
+
+        // Make sure the exception handler is called.
+        tasks.addTask(() -> {
+            throw new RuntimeException("XXX");
+        });
+        assertTrue(tasks.waitOnAllTasks());
+        assertNotNull(thrown.get());
+        MoreAsserts.assertContainsRegex("XXX", thrown.get().getMessage());
+    }
+}