ShortcutManager: finishing touches

- Change back the throttling quota to 10 calls / day
- Foreground apps are no longer throttled, and when an app comes to
foreground the call counter will be reset.
- When the system locale changes, reset throttling for all packages
for all users.
  See LocalService.onSystemLocaleChangedNoLock() for how it's performed.
  Because the reset must happen before any other apps have a chance to
  publish shortcuts, the logic is not straightforward.

- Added an internal API to reset the throttling upon inline-reply
from a notification.

- Stop supporting icons from "content:" URIs
- Improved javadoc on several APIs.

Also internal refactor needed to this:
- ShortcutUser.getAllPackages()/getAllLaunchers() are no longer
accessible to outer code to prevent accidentally adding/removing the
content.  Outer code should use forAllPackages() / forAllLaunchers().

Bug 27923857

Change-Id: I002511193d1d33718163bb1dabe77610bde58198
diff --git a/core/java/android/content/pm/IShortcutService.aidl b/core/java/android/content/pm/IShortcutService.aidl
index 9c90346..2ba24f6 100644
--- a/core/java/android/content/pm/IShortcutService.aidl
+++ b/core/java/android/content/pm/IShortcutService.aidl
@@ -49,6 +49,8 @@
 
     void resetThrottling(); // system only API for developer opsions
 
+    void onApplicationActive(String packageName, int userId); // system only API for sysUI
+
     byte[] getBackupPayload(int user);
 
     void applyRestore(in byte[] payload, int user);
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 9b1d0f7..bd8cae2 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -20,7 +20,6 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.content.ComponentName;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Icon;
@@ -35,8 +34,6 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.List;
 import java.util.Set;
 
 // TODO Enhance javadoc
@@ -307,14 +304,6 @@
             case Icon.TYPE_RESOURCE:
             case Icon.TYPE_BITMAP:
                 break; // OK
-            case Icon.TYPE_URI:
-                if (ContentResolver.SCHEME_CONTENT.equals(icon.getUri().getScheme())) {
-                    break;
-                }
-                // Note "file:" is not supported, because depending on the path, system server
-                // cannot access it. // TODO Revisit "file:" icon support
-
-                // fall through
             default:
                 throw getInvalidIconException();
         }
@@ -374,6 +363,12 @@
          * Optionally sets the target activity.  If it's not set, and if the caller application
          * has multiple launcher icons, this shortcut will be shown on all those icons.
          * If it's set, this shortcut will be only shown on this activity.
+         *
+         * <p>The package name of the target activity must match the package name of the shortcut
+         * publisher.
+         *
+         * <p>This has nothing to do with the activity that this shortcut will launch.  This is
+         * a hint to the launcher app about which launcher icon to associate this shortcut with.
          */
         @NonNull
         public Builder setActivityComponent(@NonNull ComponentName activityComponent) {
@@ -385,11 +380,8 @@
          * Optionally sets an icon.
          *
          * <ul>
-         *     <li>Tints are not supported.
-         *     <li>Bitmaps, resources and "content:" URIs are supported.
-         *     <li>"content:" URI will be fetched when a shortcut is registered to
-         *         {@link ShortcutManager}.  Changing the content from the same URI later will
-         *         not be reflected to launcher icons.
+         *     <li>Tints set by {@link Icon#setTint} or {@link Icon#setTintList} are not supported.
+         *     <li>Bitmaps and resources are supported, but "content:" URIs are not supported.
          * </ul>
          *
          * <p>For performance reasons, icons will <b>NOT</b> be available on instances
@@ -498,6 +490,11 @@
     /**
      * Return the target activity, which may be null, in which case the shortcut is not associated
      * with a specific activity.
+     *
+     * <p>This has nothing to do with the activity that this shortcut will launch.  This is
+     * a hint to the launcher app that on which launcher icon this shortcut should be shown.
+     *
+     * @see Builder#setActivityComponent
      */
     @Nullable
     public ComponentName getActivityComponent() {
@@ -550,6 +547,10 @@
      *
      * <p>All shortcuts must have an intent, but this method will return null when
      * {@link #hasKeyFieldsOnly()} is true.
+     *
+     * <p>Launcher apps <b>cannot</b> see the intent.  If a {@link ShortcutInfo} is obtained via
+     * {@link LauncherApps}, then this method will always return null.  Launcher apps can only
+     * start a shortcut intent with {@link LauncherApps#startShortcut}.
      */
     @Nullable
     public Intent getIntent() {
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index 75803d3..ab0367d 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -62,13 +62,17 @@
  * <h3>Rate limiting</h3>
  *
  * Calls to {@link #setDynamicShortcuts(List)}, {@link #addDynamicShortcuts(List)},
- * and {@link #updateShortcuts(List)} will be
+ * and {@link #updateShortcuts(List)} from <b>background applications</b> will be
  * rate-limited.  An application can call these methods at most
  * {@link #getRemainingCallCount()} times until the rate-limiting counter is reset,
- * which happens every hour.
+ * which happens at a certain time every day.
  *
  * <p>An application can use {@link #getRateLimitResetTime()} to get the next reset time.
  *
+ * <p>Foreground applications (i.e. ones with a foreground activity or a foreground services)
+ * will not be throttled. Also, when an application comes to foreground,
+ * {@link #getRemainingCallCount()} will be reset to the initial value.
+ *
  * <p>For testing purposes, use "Developer Options" (found in the Settings menu) to reset the
  * internal rate-limiting counter.  Automated tests can use the following ADB shell command to
  * achieve the same effect:</p>
diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java
index dc3d317..3f8bad1 100644
--- a/core/java/android/content/pm/ShortcutServiceInternal.java
+++ b/core/java/android/content/pm/ShortcutServiceInternal.java
@@ -23,7 +23,6 @@
 import android.content.Intent;
 import android.content.pm.LauncherApps.ShortcutQuery;
 import android.os.ParcelFileDescriptor;
-import android.os.UserHandle;
 
 import java.util.List;
 
@@ -68,4 +67,10 @@
 
     public abstract boolean hasShortcutHostPermission(int launcherUserId,
             @NonNull String callingPackage);
+
+    /**
+     * Called by AM when the system locale changes *within the AM lock*.  ABSOLUTELY do not take
+     * any locks in this method.
+     */
+    public abstract void onSystemLocaleChangedNoLock();
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 22cf279..040da4d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1989,6 +1989,11 @@
     <permission android:name="android.permission.UPDATE_CONFIG"
         android:protectionLevel="signature|privileged" />
 
+    <!-- Allows the system to reset throttling in shortcut manager.
+         @hide -->
+    <permission android:name="android.permission.RESET_SHORTCUT_MANAGER_THROTTLING"
+        android:protectionLevel="signature" />
+
     <!-- ========================================= -->
     <!-- Permissions for special development tools -->
     <!-- ========================================= -->
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 21a9f2c..233a3f8 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -141,6 +141,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.UserInfo;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
@@ -18191,6 +18192,12 @@
                         null, AppOpsManager.OP_NONE, null, false, false,
                         MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
                 if ((changes&ActivityInfo.CONFIG_LOCALE) != 0) {
+                    // Tell the shortcut manager that the system locale changed.  It needs to know
+                    // it before any other apps receive ACTION_LOCALE_CHANGED, which is why
+                    // we "push" from here, rather than having the service listen to the broadcast.
+                    LocalServices.getService(ShortcutServiceInternal.class)
+                            .onSystemLocaleChangedNoLock();
+
                     intent = new Intent(Intent.ACTION_LOCALE_CHANGED);
                     intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
                     if (!mProcessesReady) {
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index c6d66fe..76d47a8 100644
--- a/services/core/java/com/android/server/pm/ShortcutLauncher.java
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -57,15 +57,18 @@
      */
     final private ArrayMap<PackageWithUser, ArraySet<String>> mPinnedShortcuts = new ArrayMap<>();
 
-    private ShortcutLauncher(@UserIdInt int ownerUserId, @NonNull String packageName,
+    private ShortcutLauncher(@NonNull ShortcutUser shortcutUser,
+            @UserIdInt int ownerUserId, @NonNull String packageName,
             @UserIdInt int launcherUserId, ShortcutPackageInfo spi) {
-        super(launcherUserId, packageName, spi != null ? spi : ShortcutPackageInfo.newEmpty());
+        super(shortcutUser, launcherUserId, packageName,
+                spi != null ? spi : ShortcutPackageInfo.newEmpty());
         mOwnerUserId = ownerUserId;
     }
 
-    public ShortcutLauncher(@UserIdInt int ownerUserId, @NonNull String packageName,
+    public ShortcutLauncher(@NonNull ShortcutUser shortcutUser,
+            @UserIdInt int ownerUserId, @NonNull String packageName,
             @UserIdInt int launcherUserId) {
-        this(ownerUserId, packageName, launcherUserId, null);
+        this(shortcutUser, ownerUserId, packageName, launcherUserId, null);
     }
 
     @Override
@@ -179,8 +182,8 @@
     /**
      * Load.
      */
-    public static ShortcutLauncher loadFromXml(XmlPullParser parser, int ownerUserId,
-            boolean fromBackup) throws IOException, XmlPullParserException {
+    public static ShortcutLauncher loadFromXml(XmlPullParser parser, ShortcutUser shortcutUser,
+            int ownerUserId, boolean fromBackup) throws IOException, XmlPullParserException {
         final String launcherPackageName = ShortcutService.parseStringAttribute(parser,
                 ATTR_PACKAGE_NAME);
 
@@ -189,8 +192,8 @@
                 fromBackup ? ownerUserId
                 : ShortcutService.parseIntAttribute(parser, ATTR_LAUNCHER_USER_ID, ownerUserId);
 
-        final ShortcutLauncher ret = new ShortcutLauncher(launcherUserId, launcherPackageName,
-                launcherUserId);
+        final ShortcutLauncher ret = new ShortcutLauncher(shortcutUser, launcherUserId,
+                launcherPackageName, launcherUserId);
 
         ArraySet<String> ids = null;
         final int outerDepth = parser.getDepth();
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index d7f8cc6..151f61e 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -38,7 +38,6 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Set;
 import java.util.function.Predicate;
@@ -95,12 +94,21 @@
      */
     private long mLastResetTime;
 
-    private ShortcutPackage(int packageUserId, String packageName, ShortcutPackageInfo spi) {
-        super(packageUserId, packageName, spi != null ? spi : ShortcutPackageInfo.newEmpty());
+    private final int mPackageUid;
+
+    private long mLastKnownForegroundElapsedTime;
+
+    private ShortcutPackage(ShortcutService s, ShortcutUser shortcutUser,
+            int packageUserId, String packageName, ShortcutPackageInfo spi) {
+        super(shortcutUser, packageUserId, packageName,
+                spi != null ? spi : ShortcutPackageInfo.newEmpty());
+
+        mPackageUid = s.injectGetPackageUid(packageName, packageUserId);
     }
 
-    public ShortcutPackage(int packageUserId, String packageName) {
-        this(packageUserId, packageName, null);
+    public ShortcutPackage(ShortcutService s, ShortcutUser shortcutUser,
+            int packageUserId, String packageName) {
+        this(s, shortcutUser, packageUserId, packageName, null);
     }
 
     @Override
@@ -109,6 +117,10 @@
         return getPackageUserId();
     }
 
+    public int getPackageUid() {
+        return mPackageUid;
+    }
+
     /**
      * Called when a shortcut is about to be published.  At this point we know the publisher package
      * exists (as opposed to Launcher trying to fetch shortcuts from a non-existent package), so
@@ -274,18 +286,12 @@
         }
 
         // Then, for the pinned set for each launcher, set the pin flag one by one.
-        final ArrayMap<ShortcutUser.PackageWithUser, ShortcutLauncher> launchers =
-                s.getUserShortcutsLocked(getPackageUserId()).getAllLaunchers();
-
-        for (int l = launchers.size() - 1; l >= 0; l--) {
-            // Note even if a launcher that hasn't been installed can still pin shortcuts.
-
-            final ShortcutLauncher launcherShortcuts = launchers.valueAt(l);
+        s.getUserShortcutsLocked(getPackageUserId()).forAllLaunchers(launcherShortcuts -> {
             final ArraySet<String> pinned = launcherShortcuts.getPinnedShortcutIds(
                     getPackageName(), getPackageUserId());
 
             if (pinned == null || pinned.size() == 0) {
-                continue;
+                return;
             }
             for (int i = pinned.size() - 1; i >= 0; i--) {
                 final String id = pinned.valueAt(i);
@@ -299,7 +305,7 @@
                 }
                 si.addFlags(ShortcutInfo.FLAG_PINNED);
             }
-        }
+        });
 
         // Lastly, remove the ones that are no longer pinned nor dynamic.
         removeOrphans(s);
@@ -307,8 +313,28 @@
 
     /**
      * Number of calls that the caller has made, since the last reset.
+     *
+     * <p>This takes care of the resetting the counter for foreground apps as well as after
+     * locale changes.
      */
     public int getApiCallCount(@NonNull ShortcutService s) {
+        mShortcutUser.resetThrottlingIfNeeded(s);
+
+        // Reset the counter if:
+        // - the package is in foreground now.
+        // - the package is *not* in foreground now, but was in foreground at some point
+        // since the previous time it had been.
+        if (s.isUidForegroundLocked(mPackageUid)
+                || mLastKnownForegroundElapsedTime
+                    < s.getUidLastForegroundElapsedTimeLocked(mPackageUid)) {
+            mLastKnownForegroundElapsedTime = s.injectElapsedRealtime();
+            resetRateLimiting(s);
+        }
+
+        // Note resetThrottlingIfNeeded() and resetRateLimiting() will set 0 to mApiCallCount,
+        // but we just can't return 0 at this point, because we may have to update
+        // mLastResetTime.
+
         final long last = s.getLastResetTimeLocked();
 
         final long now = s.injectCurrentTimeMillis();
@@ -335,16 +361,30 @@
     /**
      * If the caller app hasn't been throttled yet, increment {@link #mApiCallCount}
      * and return true.  Otherwise just return false.
+     *
+     * <p>This takes care of the resetting the counter for foreground apps as well as after
+     * locale changes, which is done internally by {@link #getApiCallCount}.
      */
     public boolean tryApiCall(@NonNull ShortcutService s) {
         if (getApiCallCount(s) >= s.mMaxUpdatesPerInterval) {
             return false;
         }
         mApiCallCount++;
+        s.scheduleSaveUser(getOwnerUserId());
         return true;
     }
 
-    public void resetRateLimitingForCommandLine() {
+    public void resetRateLimiting(@NonNull ShortcutService s) {
+        if (ShortcutService.DEBUG) {
+            Slog.d(TAG, "resetRateLimiting: " + getPackageName());
+        }
+        if (mApiCallCount > 0) {
+            mApiCallCount = 0;
+            s.scheduleSaveUser(getOwnerUserId());
+        }
+    }
+
+    public void resetRateLimitingForCommandLineNoSaving() {
         mApiCallCount = 0;
         mLastResetTime = 0;
     }
@@ -451,6 +491,8 @@
         pw.print(prefix);
         pw.print("Package: ");
         pw.print(getPackageName());
+        pw.print("  UID: ");
+        pw.print(mPackageUid);
         pw.println();
 
         pw.print(prefix);
@@ -459,6 +501,13 @@
         pw.print(getApiCallCount(s));
         pw.println();
 
+        // getApiCallCount() may have updated mLastKnownForegroundElapsedTime.
+        pw.print(prefix);
+        pw.print("  ");
+        pw.print("Last known FG: ");
+        pw.print(mLastKnownForegroundElapsedTime);
+        pw.println();
+
         // This should be after getApiCallCount(), which may update it.
         pw.print(prefix);
         pw.print("  ");
@@ -571,14 +620,15 @@
         out.endTag(null, TAG_SHORTCUT);
     }
 
-    public static ShortcutPackage loadFromXml(ShortcutService s, XmlPullParser parser,
-            int ownerUserId, boolean fromBackup)
+    public static ShortcutPackage loadFromXml(ShortcutService s, ShortcutUser shortcutUser,
+            XmlPullParser parser, boolean fromBackup)
             throws IOException, XmlPullParserException {
 
         final String packageName = ShortcutService.parseStringAttribute(parser,
                 ATTR_NAME);
 
-        final ShortcutPackage ret = new ShortcutPackage(ownerUserId, packageName);
+        final ShortcutPackage ret = new ShortcutPackage(s, shortcutUser,
+                shortcutUser.getUserId(), packageName);
 
         ret.mDynamicShortcutCount =
                 ShortcutService.parseIntAttribute(parser, ATTR_DYNAMIC_COUNT);
@@ -602,7 +652,8 @@
                         ret.getPackageInfo().loadFromXml(parser, fromBackup);
                         continue;
                     case TAG_SHORTCUT:
-                        final ShortcutInfo si = parseShortcut(parser, packageName, ownerUserId);
+                        final ShortcutInfo si = parseShortcut(parser, packageName,
+                                shortcutUser.getUserId());
 
                         // Don't use addShortcut(), we don't need to save the icon.
                         ret.mShortcuts.put(si.getId(), si);
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
index f31dd17..6fbdb82 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
@@ -34,8 +34,12 @@
 
     private final ShortcutPackageInfo mPackageInfo;
 
-    protected ShortcutPackageItem(int packageUserId, @NonNull String packageName,
+    protected final ShortcutUser mShortcutUser;
+
+    protected ShortcutPackageItem(@NonNull ShortcutUser shortcutUser,
+            int packageUserId, @NonNull String packageName,
             @NonNull ShortcutPackageInfo packageInfo) {
+        mShortcutUser = shortcutUser;
         mPackageUserId = packageUserId;
         mPackageName = Preconditions.checkStringNotEmpty(packageName);
         mPackageInfo = Preconditions.checkNotNull(packageInfo);
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index c0874ef..0f17804 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -19,9 +19,10 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
 import android.app.AppGlobals;
+import android.app.IUidObserver;
 import android.content.ComponentName;
-import android.content.ContentProvider;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -39,11 +40,9 @@
 import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.CompressFormat;
-import android.graphics.BitmapFactory;
 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;
@@ -56,16 +55,18 @@
 import android.os.ResultReceiver;
 import android.os.SELinux;
 import android.os.ShellCommand;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.text.TextUtils;
 import android.text.format.Time;
-import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.KeyValueListParser;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.util.SparseLongArray;
 import android.util.TypedValue;
 import android.util.Xml;
 
@@ -73,6 +74,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
@@ -102,6 +104,7 @@
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
@@ -124,12 +127,13 @@
 
     static final boolean DEBUG = false; // STOPSHIP if true
     static final boolean DEBUG_LOAD = false; // STOPSHIP if true
+    static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true
 
     @VisibleForTesting
-    static final long DEFAULT_RESET_INTERVAL_SEC = 60 * 60; // 1 hour
+    static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day
 
     @VisibleForTesting
-    static final int DEFAULT_MAX_UPDATES_PER_INTERVAL = 2;
+    static final int DEFAULT_MAX_UPDATES_PER_INTERVAL = 10;
 
     @VisibleForTesting
     static final int DEFAULT_MAX_SHORTCUTS_PER_APP = 5;
@@ -162,6 +166,7 @@
 
     private static final String TAG_ROOT = "root";
     private static final String TAG_LAST_RESET_TIME = "last_reset_time";
+    private static final String TAG_LOCALE_CHANGE_SEQUENCE_NUMBER = "locale_seq_no";
 
     private static final String ATTR_VALUE = "value";
 
@@ -256,8 +261,23 @@
     private final UserManager mUserManager;
 
     @GuardedBy("mLock")
+    final SparseIntArray mUidState = new SparseIntArray();
+
+    @GuardedBy("mLock")
+    final SparseLongArray mUidLastForegroundElapsedTime = new SparseLongArray();
+
+    @GuardedBy("mLock")
     private List<Integer> mDirtyUserIds = new ArrayList<>();
 
+    /**
+     * A counter that increments every time the system locale changes.  We keep track of it to reset
+     * throttling counters on the first call from each package after the last locale change.
+     *
+     * We need this mechanism because we can't do much in the locale change callback, which is
+     * {@link ShortcutServiceInternal#onSystemLocaleChangedNoLock()}.
+     */
+    private final AtomicLong mLocaleChangeSequenceNumber = new AtomicLong();
+
     private static final int PACKAGE_MATCH_FLAGS =
             PackageManager.MATCH_DIRECT_BOOT_AWARE
             | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
@@ -283,6 +303,9 @@
     @GuardedBy("mStatLock")
     private final long[] mDurationStats = new long[Stats.COUNT];
 
+    private static final int PROCESS_STATE_FOREGROUND_THRESHOLD =
+            ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+
     public ShortcutService(Context context) {
         this(context, BackgroundThread.get().getLooper());
     }
@@ -297,6 +320,9 @@
         mUserManager = context.getSystemService(UserManager.class);
 
         mPackageMonitor.register(context, looper, UserHandle.ALL, /* externalStorage= */ false);
+
+        injectRegisterUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_PROCSTATE
+                | ActivityManager.UID_OBSERVER_GONE);
     }
 
     void logDurationStat(int statId, long start) {
@@ -306,6 +332,59 @@
         }
     }
 
+    public long getLocaleChangeSequenceNumber() {
+        return mLocaleChangeSequenceNumber.get();
+    }
+
+    final private IUidObserver mUidObserver = new IUidObserver.Stub() {
+        @Override public void onUidStateChanged(int uid, int procState) throws RemoteException {
+            handleOnUidStateChanged(uid, procState);
+        }
+
+        @Override public void onUidGone(int uid) throws RemoteException {
+            handleOnUidStateChanged(uid, ActivityManager.MAX_PROCESS_STATE);
+        }
+
+        @Override public void onUidActive(int uid) throws RemoteException {
+        }
+
+        @Override public void onUidIdle(int uid) throws RemoteException {
+        }
+    };
+
+    void handleOnUidStateChanged(int uid, int procState) {
+        if (DEBUG_PROCSTATE) {
+            Slog.d(TAG, "onUidStateChanged: uid=" + uid + " state=" + procState);
+        }
+        synchronized (mLock) {
+            mUidState.put(uid, procState);
+
+            // We need to keep track of last time an app comes to foreground.
+            // See ShortcutPackage.getApiCallCount() for how it's used.
+            // It doesn't have to be persisted, but it needs to be the elapsed time.
+            if (isProcessStateForeground(procState)) {
+                mUidLastForegroundElapsedTime.put(uid, injectElapsedRealtime());
+            }
+        }
+    }
+
+    private boolean isProcessStateForeground(int processState) {
+        return processState <= PROCESS_STATE_FOREGROUND_THRESHOLD;
+    }
+
+    boolean isUidForegroundLocked(int uid) {
+        if (uid == Process.SYSTEM_UID) {
+            // IUidObserver doesn't report the state of SYSTEM, but it always has bound services,
+            // so it's foreground anyway.
+            return true;
+        }
+        return isProcessStateForeground(mUidState.get(uid, ActivityManager.MAX_PROCESS_STATE));
+    }
+
+    long getUidLastForegroundElapsedTimeLocked(int uid) {
+        return mUidLastForegroundElapsedTime.get(uid);
+    }
+
     /**
      * System service lifecycle.
      */
@@ -596,6 +675,8 @@
 
             // Body.
             writeTagValue(out, TAG_LAST_RESET_TIME, mRawLastResetTime);
+            writeTagValue(out, TAG_LOCALE_CHANGE_SEQUENCE_NUMBER,
+                    mLocaleChangeSequenceNumber.get());
 
             // Epilogue.
             out.endTag(null, TAG_ROOT);
@@ -640,6 +721,9 @@
                     case TAG_LAST_RESET_TIME:
                         mRawLastResetTime = parseLongAttribute(parser, ATTR_VALUE);
                         break;
+                    case TAG_LOCALE_CHANGE_SEQUENCE_NUMBER:
+                        mLocaleChangeSequenceNumber.set(parseLongAttribute(parser, ATTR_VALUE));
+                        break;
                     default:
                         Slog.e(TAG, "Invalid tag: " + tag);
                         break;
@@ -993,20 +1077,6 @@
                         bitmap = icon.getBitmap(); // Don't recycle in this case.
                         break;
                     }
-                    case Icon.TYPE_URI: {
-                        final Uri uri = ContentProvider.maybeAddUserId(icon.getUri(), userId);
-
-                        try (InputStream is = mContext.getContentResolver().openInputStream(uri)) {
-
-                            bitmapToRecycle = BitmapFactory.decodeStream(is);
-                            bitmap = bitmapToRecycle;
-
-                        } catch (IOException e) {
-                            Slog.e(TAG, "Unable to load icon from " + uri);
-                            return;
-                        }
-                        break;
-                    }
                     default:
                         // This shouldn't happen because we've already validated the icon, but
                         // just in case.
@@ -1122,6 +1192,24 @@
         Preconditions.checkState(isCallerSystem(), "Caller must be system");
     }
 
+    private void enforceResetThrottlingPermission() {
+        if (isCallerSystem()) {
+            return;
+        }
+        injectEnforceCallingPermission(
+                android.Manifest.permission.RESET_SHORTCUT_MANAGER_THROTTLING, null);
+    }
+
+    /**
+     * Somehow overriding ServiceContext.enforceCallingPermission() in the unit tests would confuse
+     * mockito.  So instead we extracted it here and override it in the tests.
+     */
+    @VisibleForTesting
+    void injectEnforceCallingPermission(
+            @NonNull String permission, @Nullable String message) {
+        mContext.enforceCallingPermission(permission, message);
+    }
+
     private void verifyCaller(@NonNull String packageName, @UserIdInt int userId) {
         Preconditions.checkStringNotEmpty(packageName, "packageName");
 
@@ -1481,6 +1569,23 @@
         Slog.i(TAG, "ShortcutManager: throttling counter reset for all users");
     }
 
+    void resetPackageThrottling(String packageName, int userId) {
+        synchronized (mLock) {
+            getPackageShortcutsLocked(packageName, userId)
+                    .resetRateLimitingForCommandLineNoSaving();
+            saveUserLocked(userId);
+        }
+    }
+
+    @Override
+    public void onApplicationActive(String packageName, int userId) {
+        if (DEBUG) {
+            Slog.d(TAG, "onApplicationActive: package=" + packageName + "  userid=" + userId);
+        }
+        enforceResetThrottlingPermission();
+        resetPackageThrottling(packageName, userId);
+    }
+
     // We override this method in unit tests to do a simpler check.
     boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) {
         return hasShortcutHostPermissionInner(callingPackage, userId);
@@ -1593,15 +1698,11 @@
         user.removeLauncher(packageUserId, packageName);
 
         // Then remove pinned shortcuts from all launchers.
-        final ArrayMap<PackageWithUser, ShortcutLauncher> launchers = user.getAllLaunchers();
-        for (int i = launchers.size() - 1; i >= 0; i--) {
-            launchers.valueAt(i).cleanUpPackage(packageName, packageUserId);
-        }
-        // Now there may be orphan shortcuts because we removed pinned shortucts at the previous
+        user.forAllLaunchers(l -> l.cleanUpPackage(packageName, packageUserId));
+
+        // Now there may be orphan shortcuts because we removed pinned shortcuts at the previous
         // step.  Remove them too.
-        for (int i = user.getAllPackages().size() - 1; i >= 0; i--) {
-            user.getAllPackages().valueAt(i).refreshPinnedFlags(this);
-        }
+        user.forAllPackages(p -> p.refreshPinnedFlags(this));
 
         scheduleSaveUser(owningUserId);
 
@@ -1644,13 +1745,12 @@
                             callingPackage, packageName, shortcutIds, changedSince,
                             componentName, queryFlags, userId, ret, cloneFlag);
                 } else {
-                    final ArrayMap<String, ShortcutPackage> packages =
-                            getUserShortcutsLocked(userId).getAllPackages();
-                    for (int i = packages.size() - 1; i >= 0; i--) {
+                    final List<String> shortcutIdsF = shortcutIds;
+                    getUserShortcutsLocked(userId).forAllPackages(p -> {
                         getShortcutsInnerLocked(launcherUserId,
-                                callingPackage, packages.keyAt(i), shortcutIds, changedSince,
+                                callingPackage, p.getPackageName(), shortcutIdsF, changedSince,
                                 componentName, queryFlags, userId, ret, cloneFlag);
-                    }
+                    });
                 }
             }
             return ret;
@@ -1819,6 +1919,29 @@
                 @NonNull String callingPackage) {
             return ShortcutService.this.hasShortcutHostPermission(callingPackage, launcherUserId);
         }
+
+        /**
+         * Called by AM when the system locale changes *within the AM lock.  ABSOLUTELY do not take
+         * any locks in this method.
+         */
+        @Override
+        public void onSystemLocaleChangedNoLock() {
+            // DO NOT HOLD ANY LOCKS HERE.
+
+            // We want to reset throttling for all packages for all users.  But we can't just do so
+            // here because:
+            // - We can't load/save users that are locked.
+            // - Even for loaded users, resetting the counters would require us to hold mLock.
+            //
+            // So we use a "pull" model instead.  In here, we just increment the "locale change
+            // sequence number".  Each ShortcutUser has the "last known locale change sequence".
+            //
+            // This allows ShortcutUser's to detect the system locale change, so they can reset
+            // counters.
+
+            mLocaleChangeSequenceNumber.incrementAndGet();
+            postToHandler(() -> scheduleSaveBaseState());
+        }
     }
 
     /**
@@ -2087,11 +2210,11 @@
                     + android.Manifest.permission.DUMP);
             return;
         }
-        dumpInner(pw);
+        dumpInner(pw, args);
     }
 
     @VisibleForTesting
-    void dumpInner(PrintWriter pw) {
+    void dumpInner(PrintWriter pw, String[] args) {
         synchronized (mLock) {
             final long now = injectCurrentTimeMillis();
             pw.print("Now: [");
@@ -2115,6 +2238,9 @@
             pw.print(next);
             pw.print("] ");
             pw.print(formatTime(next));
+
+            pw.print("  Locale change seq#: ");
+            pw.print(mLocaleChangeSequenceNumber.get());
             pw.println();
 
             pw.print("  Config:");
@@ -2149,6 +2275,24 @@
                 pw.println();
                 mUsers.valueAt(i).dump(this, pw, "  ");
             }
+
+            pw.println();
+            pw.println("  UID state:");
+
+            for (int i = 0; i < mUidState.size(); i++) {
+                final int uid = mUidState.keyAt(i);
+                final int state = mUidState.valueAt(i);
+                pw.print("    UID=");
+                pw.print(uid);
+                pw.print(" state=");
+                pw.print(state);
+                if (isProcessStateForeground(state)) {
+                    pw.print("  [FG]");
+                }
+                pw.print("  last FG=");
+                pw.print(mUidLastForegroundElapsedTime.get(uid));
+                pw.println();
+            }
         }
     }
 
@@ -2316,10 +2460,7 @@
 
             Slog.i(TAG, "cmd: handleResetPackageThrottling: " + packageName);
 
-            synchronized (mLock) {
-                getPackageShortcutsLocked(packageName, mUserId).resetRateLimitingForCommandLine();
-                saveUserLocked(mUserId);
-            }
+            resetPackageThrottling(packageName, mUserId);
         }
 
         private void handleOverrideConfig() throws CommandException {
@@ -2404,6 +2545,11 @@
         return System.currentTimeMillis();
     }
 
+    @VisibleForTesting
+    long injectElapsedRealtime() {
+        return SystemClock.elapsedRealtime();
+    }
+
     // Injection point.
     @VisibleForTesting
     int injectBinderCallingUid() {
@@ -2451,6 +2597,14 @@
     }
 
     @VisibleForTesting
+    void injectRegisterUidObserver(IUidObserver observer, int which) {
+        try {
+            ActivityManagerNative.getDefault().registerUidObserver(observer, which);
+        } catch (RemoteException shouldntHappen) {
+        }
+    }
+
+    @VisibleForTesting
     PackageManagerInternal injectPackageManagerInternal() {
         return mPackageManagerInternal;
     }
@@ -2500,7 +2654,7 @@
             final ShortcutUser user = mUsers.get(userId);
             if (user == null) return null;
 
-            final ShortcutPackage pkg = user.getAllPackages().get(packageName);
+            final ShortcutPackage pkg = user.getAllPackagesForTest().get(packageName);
             if (pkg == null) return null;
 
             return pkg.findShortcutById(shortcutId);
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index 3d2e2ec..7d19a78 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -21,7 +21,9 @@
 import android.text.format.Formatter;
 import android.util.ArrayMap;
 import android.util.Slog;
+import android.util.SparseArray;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 
 import libcore.util.Objects;
@@ -45,6 +47,7 @@
     private static final String TAG_LAUNCHER = "launcher";
 
     private static final String ATTR_VALUE = "value";
+    private static final String ATTR_KNOWN_LOCALE_CHANGE_SEQUENCE_NUMBER = "locale-seq-no";
 
     static final class PackageWithUser {
         final int userId;
@@ -89,10 +92,15 @@
 
     private final ArrayMap<String, ShortcutPackage> mPackages = new ArrayMap<>();
 
+    private final SparseArray<ShortcutPackage> mPackagesFromUid = new SparseArray<>();
+
     private final ArrayMap<PackageWithUser, ShortcutLauncher> mLaunchers = new ArrayMap<>();
 
+    /** Default launcher that can access the launcher apps APIs. */
     private ComponentName mLauncherComponent;
 
+    private long mKnownLocaleChangeSequenceNumber;
+
     public ShortcutUser(int userId) {
         mUserId = userId;
     }
@@ -101,7 +109,10 @@
         return mUserId;
     }
 
-    public ArrayMap<String, ShortcutPackage> getAllPackages() {
+    // We don't expose this directly to non-test code because only ShortcutUser should add to/
+    // remove from it.
+    @VisibleForTesting
+    ArrayMap<String, ShortcutPackage> getAllPackagesForTest() {
         return mPackages;
     }
 
@@ -113,7 +124,10 @@
         return removed;
     }
 
-    public ArrayMap<PackageWithUser, ShortcutLauncher> getAllLaunchers() {
+    // We don't expose this directly to non-test code because only ShortcutUser should add to/
+    // remove from it.
+    @VisibleForTesting
+    ArrayMap<PackageWithUser, ShortcutLauncher> getAllLaunchersForTest() {
         return mLaunchers;
     }
 
@@ -130,7 +144,7 @@
     public ShortcutPackage getPackageShortcuts(ShortcutService s, @NonNull String packageName) {
         ShortcutPackage ret = mPackages.get(packageName);
         if (ret == null) {
-            ret = new ShortcutPackage(mUserId, packageName);
+            ret = new ShortcutPackage(s, this, mUserId, packageName);
             mPackages.put(packageName, ret);
         } else {
             ret.attemptToRestoreIfNeededAndSave(s);
@@ -143,7 +157,7 @@
         final PackageWithUser key = PackageWithUser.of(launcherUserId, packageName);
         ShortcutLauncher ret = mLaunchers.get(key);
         if (ret == null) {
-            ret = new ShortcutLauncher(mUserId, packageName, launcherUserId);
+            ret = new ShortcutLauncher(this, mUserId, packageName, launcherUserId);
             mLaunchers.put(key, ret);
         } else {
             ret.attemptToRestoreIfNeededAndSave(s);
@@ -151,21 +165,25 @@
         return ret;
     }
 
-    public void forAllPackageItems(Consumer<ShortcutPackageItem> callback) {
-        {
-            final int size = mLaunchers.size();
-            for (int i = 0; i < size; i++) {
-                callback.accept(mLaunchers.valueAt(i));
-            }
+    public void forAllPackages(Consumer<? super ShortcutPackage> callback) {
+        final int size = mPackages.size();
+        for (int i = 0; i < size; i++) {
+            callback.accept(mPackages.valueAt(i));
         }
-        {
-            final int size = mPackages.size();
-            for (int i = 0; i < size; i++) {
-                callback.accept(mPackages.valueAt(i));
-            }
+    }
+
+    public void forAllLaunchers(Consumer<? super ShortcutLauncher> callback) {
+        final int size = mLaunchers.size();
+        for (int i = 0; i < size; i++) {
+            callback.accept(mLaunchers.valueAt(i));
         }
     }
 
+    public void forAllPackageItems(Consumer<? super ShortcutPackageItem> callback) {
+        forAllLaunchers(callback);
+        forAllPackages(callback);
+    }
+
     public void forPackageItem(@NonNull String packageName, @UserIdInt int packageUserId,
             Consumer<ShortcutPackageItem> callback) {
         forAllPackageItems(spi -> {
@@ -177,6 +195,24 @@
     }
 
     /**
+     * Reset all throttling counters for all packages, if there has been a system locale change.
+     */
+    public void resetThrottlingIfNeeded(ShortcutService s) {
+        final long currentNo = s.getLocaleChangeSequenceNumber();
+        if (mKnownLocaleChangeSequenceNumber < currentNo) {
+            if (ShortcutService.DEBUG) {
+                Slog.d(TAG, "LocaleChange detected for user " + mUserId);
+            }
+
+            mKnownLocaleChangeSequenceNumber = currentNo;
+
+            forAllPackages(p -> p.resetRateLimiting(s));
+
+            s.scheduleSaveUser(mUserId);
+        }
+    }
+
+    /**
      * Called when a package is updated.
      */
     public void handlePackageUpdated(ShortcutService s, @NonNull String packageName,
@@ -198,6 +234,9 @@
             throws IOException, XmlPullParserException {
         out.startTag(null, TAG_ROOT);
 
+        ShortcutService.writeAttr(out, ATTR_KNOWN_LOCALE_CHANGE_SEQUENCE_NUMBER,
+                mKnownLocaleChangeSequenceNumber);
+
         ShortcutService.writeTagValue(out, TAG_LAUNCHER,
                 mLauncherComponent);
 
@@ -235,6 +274,9 @@
             boolean fromBackup) throws IOException, XmlPullParserException {
         final ShortcutUser ret = new ShortcutUser(userId);
 
+        ret.mKnownLocaleChangeSequenceNumber = ShortcutService.parseLongAttribute(parser,
+                ATTR_KNOWN_LOCALE_CHANGE_SEQUENCE_NUMBER);
+
         final int outerDepth = parser.getDepth();
         int type;
         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -254,7 +296,7 @@
                     }
                     case ShortcutPackage.TAG_ROOT: {
                         final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml(
-                                s, parser, userId, fromBackup);
+                                s, ret, parser, fromBackup);
 
                         // Don't use addShortcut(), we don't need to save the icon.
                         ret.mPackages.put(shortcuts.getPackageName(), shortcuts);
@@ -262,7 +304,8 @@
                     }
 
                     case ShortcutLauncher.TAG_ROOT: {
-                        ret.addLauncher(ShortcutLauncher.loadFromXml(parser, userId, fromBackup));
+                        ret.addLauncher(
+                                ShortcutLauncher.loadFromXml(parser, ret, userId, fromBackup));
                         continue;
                     }
                 }
@@ -294,6 +337,8 @@
         pw.print(prefix);
         pw.print("User: ");
         pw.print(mUserId);
+        pw.print("  Known locale seq#: ");
+        pw.print(mKnownLocaleChangeSequenceNumber);
         pw.println();
 
         prefix += prefix + "  ";
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
index 13518b5..ced7cf0 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
@@ -56,11 +56,14 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.Manifest.permission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.UserIdInt;
 import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.IUidObserver;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -94,6 +97,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.test.InstrumentationTestCase;
+import android.test.MoreAsserts;
 import android.test.mock.MockContext;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
@@ -225,6 +229,7 @@
     /** ShortcutService with injection override methods. */
     private final class ShortcutServiceTestable extends ShortcutService {
         final ServiceContext mContext;
+        IUidObserver mUidObserver;
 
         public ShortcutServiceTestable(ServiceContext context, Looper looper) {
             super(context, looper);
@@ -265,6 +270,13 @@
         }
 
         @Override
+        long injectElapsedRealtime() {
+            // TODO This should be kept separately from mInjectedCurrentTimeLillis, since
+            // this should increase even if we rewind mInjectedCurrentTimeLillis in some tests.
+            return mInjectedCurrentTimeLillis - START_TIME;
+        }
+
+        @Override
         int injectBinderCallingUid() {
             return mInjectedCallingUid;
         }
@@ -295,6 +307,11 @@
         }
 
         @Override
+        void injectRegisterUidObserver(IUidObserver observer, int which) {
+            mUidObserver = observer;
+        }
+
+        @Override
         PackageManagerInternal injectPackageManagerInternal() {
             return mMockPackageManagerInternal;
         }
@@ -324,6 +341,13 @@
         }
 
         @Override
+        void injectEnforceCallingPermission(String permission, String message) {
+            if (!mCallerPermissions.contains(permission)) {
+                throw new SecurityException("Missing permission: " + permission);
+            }
+        }
+
+        @Override
         void wtf(String message, Exception e) {
             // During tests, WTF is fatal.
             fail(message + "  exception: " + e);
@@ -493,6 +517,8 @@
 
     private static final ShortcutQuery QUERY_ALL = new ShortcutQuery();
 
+    private final ArrayList<String> mCallerPermissions = new ArrayList<>();
+
     static {
         QUERY_ALL.setQueryFlags(
                 ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_PINNED);
@@ -561,6 +587,11 @@
 
         initService();
         setCaller(CALLING_PACKAGE_1);
+
+        // In order to complicate the situation, we set mLocaleChangeSequenceNumber to 1 by
+        // calling this.  Running test with mLocaleChangeSequenceNumber == 0 might make us miss
+        // some edge cases.
+        mInternal.onSystemLocaleChangedNoLock();
     }
 
     private static UserInfo withProfileGroupId(UserInfo in, int groupId) {
@@ -761,7 +792,7 @@
 
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
         final PrintWriter pw = new PrintWriter(out);
-        mService.dumpInner(pw);
+        mService.dumpInner(pw, null);
         pw.close();
 
         Log.e(TAG, "Dumping ShortcutService: " + message);
@@ -3546,17 +3577,17 @@
         // Check the registered packages.
         dumpsysOnLogcat();
         assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
-                hashSet(user0.getAllPackages().keySet()));
+                hashSet(user0.getAllPackagesForTest().keySet()));
         assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
-                hashSet(user10.getAllPackages().keySet()));
+                hashSet(user10.getAllPackagesForTest().keySet()));
         assertEquals(
                 set(PackageWithUser.of(USER_0, LAUNCHER_1),
                         PackageWithUser.of(USER_0, LAUNCHER_2)),
-                hashSet(user0.getAllLaunchers().keySet()));
+                hashSet(user0.getAllLaunchersForTest().keySet()));
         assertEquals(
                 set(PackageWithUser.of(USER_10, LAUNCHER_1),
                         PackageWithUser.of(USER_10, LAUNCHER_2)),
-                hashSet(user10.getAllLaunchers().keySet()));
+                hashSet(user10.getAllLaunchersForTest().keySet()));
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
                 "s0_1", "s0_2");
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
@@ -3578,17 +3609,17 @@
 
         // No changes.
         assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
-                hashSet(user0.getAllPackages().keySet()));
+                hashSet(user0.getAllPackagesForTest().keySet()));
         assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
-                hashSet(user10.getAllPackages().keySet()));
+                hashSet(user10.getAllPackagesForTest().keySet()));
         assertEquals(
                 set(PackageWithUser.of(USER_0, LAUNCHER_1),
                         PackageWithUser.of(USER_0, LAUNCHER_2)),
-                hashSet(user0.getAllLaunchers().keySet()));
+                hashSet(user0.getAllLaunchersForTest().keySet()));
         assertEquals(
                 set(PackageWithUser.of(USER_10, LAUNCHER_1),
                         PackageWithUser.of(USER_10, LAUNCHER_2)),
-                hashSet(user10.getAllLaunchers().keySet()));
+                hashSet(user10.getAllLaunchersForTest().keySet()));
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
                 "s0_1", "s0_2");
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
@@ -3609,17 +3640,17 @@
         mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_0, USER_0);
 
         assertEquals(set(CALLING_PACKAGE_2),
-                hashSet(user0.getAllPackages().keySet()));
+                hashSet(user0.getAllPackagesForTest().keySet()));
         assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
-                hashSet(user10.getAllPackages().keySet()));
+                hashSet(user10.getAllPackagesForTest().keySet()));
         assertEquals(
                 set(PackageWithUser.of(USER_0, LAUNCHER_1),
                         PackageWithUser.of(USER_0, LAUNCHER_2)),
-                hashSet(user0.getAllLaunchers().keySet()));
+                hashSet(user0.getAllLaunchersForTest().keySet()));
         assertEquals(
                 set(PackageWithUser.of(USER_10, LAUNCHER_1),
                         PackageWithUser.of(USER_10, LAUNCHER_2)),
-                hashSet(user10.getAllLaunchers().keySet()));
+                hashSet(user10.getAllLaunchersForTest().keySet()));
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
                 "s0_2");
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
@@ -3640,16 +3671,16 @@
         mService.cleanUpPackageLocked(LAUNCHER_1, USER_10, USER_10);
 
         assertEquals(set(CALLING_PACKAGE_2),
-                hashSet(user0.getAllPackages().keySet()));
+                hashSet(user0.getAllPackagesForTest().keySet()));
         assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
-                hashSet(user10.getAllPackages().keySet()));
+                hashSet(user10.getAllPackagesForTest().keySet()));
         assertEquals(
                 set(PackageWithUser.of(USER_0, LAUNCHER_1),
                         PackageWithUser.of(USER_0, LAUNCHER_2)),
-                hashSet(user0.getAllLaunchers().keySet()));
+                hashSet(user0.getAllLaunchersForTest().keySet()));
         assertEquals(
                 set(PackageWithUser.of(USER_10, LAUNCHER_2)),
-                hashSet(user10.getAllLaunchers().keySet()));
+                hashSet(user10.getAllLaunchersForTest().keySet()));
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
                 "s0_2");
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
@@ -3668,16 +3699,16 @@
         mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_10, USER_10);
 
         assertEquals(set(CALLING_PACKAGE_2),
-                hashSet(user0.getAllPackages().keySet()));
+                hashSet(user0.getAllPackagesForTest().keySet()));
         assertEquals(set(CALLING_PACKAGE_1),
-                hashSet(user10.getAllPackages().keySet()));
+                hashSet(user10.getAllPackagesForTest().keySet()));
         assertEquals(
                 set(PackageWithUser.of(USER_0, LAUNCHER_1),
                         PackageWithUser.of(USER_0, LAUNCHER_2)),
-                hashSet(user0.getAllLaunchers().keySet()));
+                hashSet(user0.getAllLaunchersForTest().keySet()));
         assertEquals(
                 set(PackageWithUser.of(USER_10, LAUNCHER_2)),
-                hashSet(user10.getAllLaunchers().keySet()));
+                hashSet(user10.getAllLaunchersForTest().keySet()));
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
                 "s0_2");
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
@@ -3696,16 +3727,16 @@
         mService.cleanUpPackageLocked(LAUNCHER_2, USER_10, USER_10);
 
         assertEquals(set(CALLING_PACKAGE_2),
-                hashSet(user0.getAllPackages().keySet()));
+                hashSet(user0.getAllPackagesForTest().keySet()));
         assertEquals(set(CALLING_PACKAGE_1),
-                hashSet(user10.getAllPackages().keySet()));
+                hashSet(user10.getAllPackagesForTest().keySet()));
         assertEquals(
                 set(PackageWithUser.of(USER_0, LAUNCHER_1),
                         PackageWithUser.of(USER_0, LAUNCHER_2)),
-                hashSet(user0.getAllLaunchers().keySet()));
+                hashSet(user0.getAllLaunchersForTest().keySet()));
         assertEquals(
                 set(),
-                hashSet(user10.getAllLaunchers().keySet()));
+                hashSet(user10.getAllLaunchersForTest().keySet()));
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
                 "s0_2");
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
@@ -3724,15 +3755,15 @@
         mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_10, USER_10);
 
         assertEquals(set(CALLING_PACKAGE_2),
-                hashSet(user0.getAllPackages().keySet()));
+                hashSet(user0.getAllPackagesForTest().keySet()));
         assertEquals(set(),
-                hashSet(user10.getAllPackages().keySet()));
+                hashSet(user10.getAllPackagesForTest().keySet()));
         assertEquals(
                 set(PackageWithUser.of(USER_0, LAUNCHER_1),
                         PackageWithUser.of(USER_0, LAUNCHER_2)),
-                hashSet(user0.getAllLaunchers().keySet()));
+                hashSet(user0.getAllLaunchersForTest().keySet()));
         assertEquals(set(),
-                hashSet(user10.getAllLaunchers().keySet()));
+                hashSet(user10.getAllLaunchersForTest().keySet()));
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
                 "s0_2");
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
@@ -4654,19 +4685,19 @@
     private void checkBackupAndRestore_success() {
         // Make sure non-system user is not restored.
         final ShortcutUser userP0 = mService.getUserShortcutsLocked(USER_P0);
-        assertEquals(0, userP0.getAllPackages().size());
-        assertEquals(0, userP0.getAllLaunchers().size());
+        assertEquals(0, userP0.getAllPackagesForTest().size());
+        assertEquals(0, userP0.getAllLaunchersForTest().size());
 
         // Make sure only "allowBackup" apps are restored, and are shadow.
         final ShortcutUser user0 = mService.getUserShortcutsLocked(USER_0);
-        assertExistsAndShadow(user0.getAllPackages().get(CALLING_PACKAGE_1));
-        assertExistsAndShadow(user0.getAllPackages().get(CALLING_PACKAGE_2));
-        assertExistsAndShadow(user0.getAllLaunchers().get(PackageWithUser.of(USER_0, LAUNCHER_1)));
-        assertExistsAndShadow(user0.getAllLaunchers().get(PackageWithUser.of(USER_0, LAUNCHER_2)));
+        assertExistsAndShadow(user0.getAllPackagesForTest().get(CALLING_PACKAGE_1));
+        assertExistsAndShadow(user0.getAllPackagesForTest().get(CALLING_PACKAGE_2));
+        assertExistsAndShadow(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_0, LAUNCHER_1)));
+        assertExistsAndShadow(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_0, LAUNCHER_2)));
 
-        assertNull(user0.getAllPackages().get(CALLING_PACKAGE_3));
-        assertNull(user0.getAllLaunchers().get(PackageWithUser.of(USER_0, LAUNCHER_3)));
-        assertNull(user0.getAllLaunchers().get(PackageWithUser.of(USER_P0, LAUNCHER_1)));
+        assertNull(user0.getAllPackagesForTest().get(CALLING_PACKAGE_3));
+        assertNull(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_0, LAUNCHER_3)));
+        assertNull(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_P0, LAUNCHER_1)));
 
         installPackage(USER_0, CALLING_PACKAGE_1);
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -5285,6 +5316,361 @@
         });
     }
 
+    public void testThrottling_localeChanges() {
+        prepareCrossProfileDataSet();
+
+        dumpsysOnLogcat("Before save & load");
+
+        mService.saveDirtyInfo();
+        initService();
+
+        final long origSequenceNumber = mService.getLocaleChangeSequenceNumber();
+
+        mInternal.onSystemLocaleChangedNoLock();
+
+        assertEquals(origSequenceNumber + 1, mService.getLocaleChangeSequenceNumber());
+
+        // Note at this point only user-0 is loaded, and the counters are reset for this user,
+        // but it will work for other users too, because we persist when
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+
+        mService.saveDirtyInfo();
+        initService();
+
+        // Make sure the counter is persisted.
+        assertEquals(origSequenceNumber + 1, mService.getLocaleChangeSequenceNumber());
+    }
+
+    public void testThrottling_foreground() throws Exception {
+        prepareCrossProfileDataSet();
+
+        dumpsysOnLogcat("Before save & load");
+
+        mService.saveDirtyInfo();
+        initService();
+
+        // We need to update the current time from time to time, since some of the internal checks
+        // rely on the time being correctly incremented.
+        mInjectedCurrentTimeLillis++;
+
+        // First, all packages have less than 3 (== initial value) remaining calls.
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+
+        mInjectedCurrentTimeLillis++;
+
+        // State changed, but not foreground, so no resetting.
+        mService.mUidObserver.onUidStateChanged(
+                CALLING_UID_1, ActivityManager.PROCESS_STATE_TOP_SLEEPING);
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+
+        mInjectedCurrentTimeLillis++;
+
+        // State changed, package1 foreground, reset.
+        mService.mUidObserver.onUidStateChanged(
+                CALLING_UID_1, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        mService.mUidObserver.onUidStateChanged(
+                CALLING_UID_1, ActivityManager.PROCESS_STATE_TOP_SLEEPING);
+
+        mInjectedCurrentTimeLillis++;
+
+        // Different app comes to foreground briefly, and goes back to background.
+        // Now, make sure package 2's counter is reset, even in this case.
+        mService.mUidObserver.onUidStateChanged(
+                CALLING_UID_2, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+        mService.mUidObserver.onUidStateChanged(
+                CALLING_UID_2, ActivityManager.PROCESS_STATE_TOP_SLEEPING);
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+
+        mInjectedCurrentTimeLillis++;
+
+        // Do the same thing one more time.  This would catch the bug with mixuing up
+        // the current time and the elapsed time.
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            mManager.updateShortcuts(list(makeShortcut("s")));
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+
+        mService.mUidObserver.onUidStateChanged(
+                CALLING_UID_2, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+        mService.mUidObserver.onUidStateChanged(
+                CALLING_UID_2, ActivityManager.PROCESS_STATE_TOP_SLEEPING);
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+
+        mInjectedCurrentTimeLillis++;
+
+        // Package 1 on user-10 comes to foreground.
+        // Now, also try calling some APIs and make sure foreground apps don't get throttled.
+        mService.mUidObserver.onUidStateChanged(
+                UserHandle.getUid(USER_10, CALLING_UID_1),
+                ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+
+            assertEquals(0, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+
+            assertEquals(0, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+
+            assertEquals(0, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+
+            assertEquals(0, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+
+            assertEquals(0, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+
+            assertEquals(3, mManager.getRemainingCallCount()); // Still 3!
+        });
+    }
+
+
+    public void testThrottling_resetByInternalCall() throws Exception {
+        prepareCrossProfileDataSet();
+
+        dumpsysOnLogcat("Before save & load");
+
+        mService.saveDirtyInfo();
+        initService();
+
+        // First, all packages have less than 3 (== initial value) remaining calls.
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+
+        // Simulate a call from sys UI.
+        mCallerPermissions.add(permission.RESET_SHORTCUT_MANAGER_THROTTLING);
+        mService.onApplicationActive(CALLING_PACKAGE_1, USER_0);
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+
+        mService.onApplicationActive(CALLING_PACKAGE_3, USER_0);
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+
+        mService.onApplicationActive(CALLING_PACKAGE_1, USER_10);
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+    }
+
+    public void testOnApplicationActive_permission() {
+        assertExpectException(SecurityException.class, "Missing permission", () ->
+            mService.onApplicationActive(CALLING_PACKAGE_1, USER_0));
+
+        // Has permission, now it should pass.
+        mCallerPermissions.add(permission.RESET_SHORTCUT_MANAGER_THROTTLING);
+        mService.onApplicationActive(CALLING_PACKAGE_1, USER_0);
+    }
+
     // ShortcutInfo tests
 
     public void testShortcutInfoMissingMandatoryFields() {
@@ -5324,7 +5710,7 @@
         si = new ShortcutInfo.Builder(getTestContext())
                 .setId("id")
                 .setActivityComponent(new ComponentName("a", "b"))
-                .setIcon(Icon.createWithContentUri("content://a.b.c/"))
+                .setIcon(Icon.createWithResource(mClientContext, 123))
                 .setTitle("title")
                 .setText("text")
                 .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
@@ -5341,7 +5727,7 @@
         assertEquals(getTestContext().getPackageName(), si.getPackageName());
         assertEquals("id", si.getId());
         assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
-        assertEquals("content://a.b.c/", si.getIcon().getUriString());
+        assertEquals(123, si.getIcon().getResId());
         assertEquals("title", si.getTitle());
         assertEquals("text", si.getText());
         assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
@@ -5363,7 +5749,7 @@
         ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
                 .setId("id")
                 .setActivityComponent(new ComponentName("a", "b"))
-                .setIcon(Icon.createWithContentUri("content://a.b.c/"))
+                .setIcon(Icon.createWithResource(mClientContext, 123))
                 .setTitle("title")
                 .setText("text")
                 .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
@@ -5382,7 +5768,7 @@
         assertEquals(mClientContext.getPackageName(), si.getPackageName());
         assertEquals("id", si.getId());
         assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
-        assertEquals("content://a.b.c/", si.getIcon().getUriString());
+        assertEquals(123, si.getIcon().getResId());
         assertEquals("title", si.getTitle());
         assertEquals("text", si.getText());
         assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
@@ -5498,7 +5884,7 @@
         ShortcutInfo sorig = new ShortcutInfo.Builder(getTestContext())
                 .setId("id")
                 .setActivityComponent(new ComponentName("a", "b"))
-                .setIcon(Icon.createWithContentUri("content://a.b.c/"))
+                .setIcon(Icon.createWithResource(mClientContext, 123))
                 .setTitle("title")
                 .setText("text")
                 .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
@@ -5520,9 +5906,9 @@
 
         si = sorig.clone(/* flags=*/ 0);
         si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
-                .setIcon(Icon.createWithContentUri("content://x.y.z/")).build());
+                .setIcon(Icon.createWithResource(mClientContext, 456)).build());
         assertEquals("text", si.getText());
-        assertEquals("content://x.y.z/", si.getIcon().getUriString());
+        assertEquals(456, si.getIcon().getResId());
 
         si = sorig.clone(/* flags=*/ 0);
         si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")