Add UserHandle to ShortcutInfo, and simplify LauncherApps APIs.

- Also fixes the bitmap recycle bug in the service.

Fixes 28053541

Change-Id: I2b244feda0f85c60e2c15af427fcad95ad5e6da5
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 4c18e15..79d9c86 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -213,8 +213,11 @@
          * Checks if the caller is in the same group as the userToCheck.
          */
         private void ensureInUserProfiles(UserHandle userToCheck, String message) {
+            ensureInUserProfiles(userToCheck.getIdentifier(), message);
+        }
+
+        private void ensureInUserProfiles(int targetUserId, String message) {
             final int callingUserId = injectCallingUserId();
-            final int targetUserId = userToCheck.getIdentifier();
 
             if (targetUserId == callingUserId) return;
 
@@ -253,9 +256,13 @@
          * Checks if the user is enabled.
          */
         private boolean isUserEnabled(UserHandle user) {
+            return isUserEnabled(user.getIdentifier());
+        }
+
+        private boolean isUserEnabled(int userId) {
             long ident = injectClearCallingIdentity();
             try {
-                UserInfo targetUserInfo = mUm.getUserInfo(user.getIdentifier());
+                UserInfo targetUserInfo = mUm.getUserInfo(userId);
                 return targetUserInfo != null && targetUserInfo.isEnabled();
             } finally {
                 injectRestoreCallingIdentity(ident);
@@ -346,8 +353,12 @@
         }
 
         private void ensureShortcutPermission(@NonNull String callingPackage, UserHandle user) {
+            ensureShortcutPermission(callingPackage, user.getIdentifier());
+        }
+
+        private void ensureShortcutPermission(@NonNull String callingPackage, int userId) {
             verifyCallingPackage(callingPackage);
-            ensureInUserProfiles(user, "Cannot start activity for unrelated profile " + user);
+            ensureInUserProfiles(userId, "Cannot start activity for unrelated profile " + userId);
 
             if (!mShortcutServiceInternal.hasShortcutHostPermission(getCallingUserId(),
                     callingPackage)) {
@@ -357,32 +368,24 @@
 
         @Override
         public ParceledListSlice getShortcuts(String callingPackage, long changedSince,
-                String packageName, ComponentName componentName, int flags, UserHandle user) {
+                String packageName, List shortcutIds, ComponentName componentName, int flags,
+                UserHandle user) {
             ensureShortcutPermission(callingPackage, user);
             if (!isUserEnabled(user)) {
                 return new ParceledListSlice<>(new ArrayList(0));
             }
+            if (shortcutIds != null && packageName == null) {
+                throw new IllegalArgumentException(
+                        "To query by shortcut ID, package name must also be set");
+            }
 
             return new ParceledListSlice<>(
                     mShortcutServiceInternal.getShortcuts(getCallingUserId(),
-                            callingPackage, changedSince, packageName,
+                            callingPackage, changedSince, packageName, shortcutIds,
                             componentName, flags, user.getIdentifier()));
         }
 
         @Override
-        public ParceledListSlice getShortcutInfo(String callingPackage, String packageName,
-                List<String> ids, UserHandle user) {
-            ensureShortcutPermission(callingPackage, user);
-            if (!isUserEnabled(user)) {
-                return new ParceledListSlice<>(new ArrayList(0));
-            }
-
-            return new ParceledListSlice<>(
-                    mShortcutServiceInternal.getShortcutInfo(getCallingUserId(),
-                            callingPackage, packageName, ids, user.getIdentifier()));
-        }
-
-        @Override
         public void pinShortcuts(String callingPackage, String packageName, List<String> ids,
                 UserHandle user) {
             ensureShortcutPermission(callingPackage, user);
@@ -396,27 +399,27 @@
         }
 
         @Override
-        public int getShortcutIconResId(String callingPackage, ShortcutInfo shortcut,
-                UserHandle user) {
-            ensureShortcutPermission(callingPackage, user);
-            if (!isUserEnabled(user)) {
+        public int getShortcutIconResId(String callingPackage, String packageName, String id,
+                int userId) {
+            ensureShortcutPermission(callingPackage, userId);
+            if (!isUserEnabled(userId)) {
                 return 0;
             }
 
             return mShortcutServiceInternal.getShortcutIconResId(getCallingUserId(),
-                    callingPackage, shortcut, user.getIdentifier());
+                    callingPackage, packageName, id, userId);
         }
 
         @Override
-        public ParcelFileDescriptor getShortcutIconFd(String callingPackage, ShortcutInfo shortcut,
-                UserHandle user) {
-            ensureShortcutPermission(callingPackage, user);
-            if (!isUserEnabled(user)) {
+        public ParcelFileDescriptor getShortcutIconFd(String callingPackage,
+                String packageName, String id, int userId) {
+            ensureShortcutPermission(callingPackage, userId);
+            if (!isUserEnabled(userId)) {
                 return null;
             }
 
             return mShortcutServiceInternal.getShortcutIconFd(getCallingUserId(),
-                    callingPackage, shortcut, user.getIdentifier());
+                    callingPackage, packageName, id, userId);
         }
 
         @Override
@@ -428,23 +431,23 @@
 
         @Override
         public boolean startShortcut(String callingPackage, String packageName, String shortcutId,
-                Rect sourceBounds, Bundle startActivityOptions, UserHandle user) {
+                Rect sourceBounds, Bundle startActivityOptions, int userId) {
             verifyCallingPackage(callingPackage);
-            ensureInUserProfiles(user, "Cannot start activity for unrelated profile " + user);
+            ensureInUserProfiles(userId, "Cannot start activity for unrelated profile " + userId);
 
-            if (!isUserEnabled(user)) {
+            if (!isUserEnabled(userId)) {
                 throw new IllegalStateException("Cannot start a shortcut for disabled profile "
-                        + user);
+                        + userId);
             }
 
             // Even without the permission, pinned shortcuts are always launchable.
             if (!mShortcutServiceInternal.isPinnedByCaller(getCallingUserId(),
-                    callingPackage, packageName, shortcutId, user.getIdentifier())) {
-                ensureShortcutPermission(callingPackage, user);
+                    callingPackage, packageName, shortcutId, userId)) {
+                ensureShortcutPermission(callingPackage, userId);
             }
 
             final Intent intent = mShortcutServiceInternal.createShortcutIntent(getCallingUserId(),
-                    callingPackage, packageName, shortcutId, user.getIdentifier());
+                    callingPackage, packageName, shortcutId, userId);
             if (intent == null) {
                 return false;
             }
@@ -455,7 +458,7 @@
 
             final long ident = Binder.clearCallingIdentity();
             try {
-                mContext.startActivityAsUser(intent, startActivityOptions, user);
+                mContext.startActivityAsUser(intent, startActivityOptions, UserHandle.of(userId));
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -768,7 +771,8 @@
                     final List<ShortcutInfo> list =
                             mShortcutServiceInternal.getShortcuts(launcherUserId,
                                     cookie.packageName,
-                                    /* changedSince= */ 0, packageName, /* component= */ null,
+                                    /* changedSince= */ 0, packageName, /* shortcutIds=*/ null,
+                                    /* component= */ null,
                                     ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY
                                     | ShortcutQuery.FLAG_GET_PINNED
                                     | ShortcutQuery.FLAG_GET_DYNAMIC
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 1076a7a..58559a5 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -17,6 +17,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ShortcutInfo;
@@ -522,7 +523,7 @@
                         ret.getPackageInfo().loadFromXml(parser, fromBackup);
                         continue;
                     case TAG_SHORTCUT:
-                        final ShortcutInfo si = parseShortcut(parser, packageName);
+                        final ShortcutInfo si = parseShortcut(parser, packageName, ownerUserId);
 
                         // Don't use addShortcut(), we don't need to save the icon.
                         ret.mShortcuts.put(si.getId(), si);
@@ -534,8 +535,8 @@
         return ret;
     }
 
-    private static ShortcutInfo parseShortcut(XmlPullParser parser, String packageName)
-            throws IOException, XmlPullParserException {
+    private static ShortcutInfo parseShortcut(XmlPullParser parser, String packageName,
+            @UserIdInt int userId) throws IOException, XmlPullParserException {
         String id;
         ComponentName activityComponent;
         // Icon icon;
@@ -586,7 +587,7 @@
             throw ShortcutService.throwForInvalidTag(depth, tag);
         }
         return new ShortcutInfo(
-                id, packageName, activityComponent, /* icon =*/ null, title, text, intent,
+                userId, id, packageName, activityComponent, /* icon =*/ null, title, text, intent,
                 intentPersistableExtras, weight, extras, lastChangedTimestamp, flags,
                 iconRes, bitmapPath);
     }
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 5c1e7a8..c124255 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -968,7 +968,8 @@
                 return; // has no icon
             }
 
-            Bitmap bitmap = null;
+            Bitmap bitmap;
+            Bitmap bitmapToRecycle = null;
             try {
                 switch (icon.getType()) {
                     case Icon.TYPE_RESOURCE: {
@@ -979,7 +980,7 @@
                         return;
                     }
                     case Icon.TYPE_BITMAP: {
-                        bitmap = icon.getBitmap();
+                        bitmap = icon.getBitmap(); // Don't recycle in this case.
                         break;
                     }
                     case Icon.TYPE_URI: {
@@ -987,7 +988,8 @@
 
                         try (InputStream is = mContext.getContentResolver().openInputStream(uri)) {
 
-                            bitmap = BitmapFactory.decodeStream(is);
+                            bitmapToRecycle = BitmapFactory.decodeStream(is);
+                            bitmap = bitmapToRecycle;
 
                         } catch (IOException e) {
                             Slog.e(TAG, "Unable to load icon from " + uri);
@@ -1011,8 +1013,14 @@
                     try {
                         path = out.getFile();
 
-                        shrinkBitmap(bitmap, mMaxIconDimension)
-                                .compress(mIconPersistFormat, mIconPersistQuality, out);
+                        Bitmap shrunk = shrinkBitmap(bitmap, mMaxIconDimension);
+                        try {
+                            shrunk.compress(mIconPersistFormat, mIconPersistQuality, out);
+                        } finally {
+                            if (bitmap != shrunk) {
+                                shrunk.recycle();
+                            }
+                        }
 
                         shortcut.setBitmapPath(out.getFile().getAbsolutePath());
                         shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_FILE);
@@ -1027,8 +1035,8 @@
                     }
                 }
             } finally {
-                if (bitmap != null) {
-                    bitmap.recycle();
+                if (bitmapToRecycle != null) {
+                    bitmapToRecycle.recycle();
                 }
                 // Once saved, we won't use the original icon information, so null it out.
                 shortcut.clearIcon();
@@ -1076,8 +1084,6 @@
 
         c.drawBitmap(in, /*src=*/ null, dst, /* paint =*/ null);
 
-        in.recycle();
-
         return scaledBitmap;
     }
 
@@ -1580,13 +1586,17 @@
         @Override
         public List<ShortcutInfo> getShortcuts(int launcherUserId,
                 @NonNull String callingPackage, long changedSince,
-                @Nullable String packageName, @Nullable ComponentName componentName,
+                @Nullable String packageName, @Nullable List<String> shortcutIds,
+                @Nullable ComponentName componentName,
                 int queryFlags, int userId) {
             final ArrayList<ShortcutInfo> ret = new ArrayList<>();
             final int cloneFlag =
                     ((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) == 0)
                             ? ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER
                             : ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO;
+            if (packageName == null) {
+                shortcutIds = null; // LauncherAppsService already threw for it though.
+            }
 
             synchronized (mLock) {
                 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
@@ -1594,14 +1604,14 @@
 
                 if (packageName != null) {
                     getShortcutsInnerLocked(launcherUserId,
-                            callingPackage, packageName, changedSince,
+                            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--) {
                         getShortcutsInnerLocked(launcherUserId,
-                                callingPackage, packages.keyAt(i), changedSince,
+                                callingPackage, packages.keyAt(i), shortcutIds, changedSince,
                                 componentName, queryFlags, userId, ret, cloneFlag);
                     }
                 }
@@ -1610,14 +1620,20 @@
         }
 
         private void getShortcutsInnerLocked(int launcherUserId, @NonNull String callingPackage,
-                @Nullable String packageName,long changedSince,
+                @Nullable String packageName, @Nullable List<String> shortcutIds, long changedSince,
                 @Nullable ComponentName componentName, int queryFlags,
                 int userId, ArrayList<ShortcutInfo> ret, int cloneFlag) {
+            final ArraySet<String> ids = shortcutIds == null ? null
+                    : new ArraySet<>(shortcutIds);
+
             getPackageShortcutsLocked(packageName, userId).findAll(ShortcutService.this, ret,
                     (ShortcutInfo si) -> {
                         if (si.getLastChangedTimestamp() < changedSince) {
                             return false;
                         }
+                        if (ids != null && !ids.contains(si.getId())) {
+                            return false;
+                        }
                         if (componentName != null
                                 && !componentName.equals(si.getActivityComponent())) {
                             return false;
@@ -1633,27 +1649,6 @@
         }
 
         @Override
-        public List<ShortcutInfo> getShortcutInfo(int launcherUserId,
-                @NonNull String callingPackage,
-                @NonNull String packageName, @Nullable List<String> ids, int userId) {
-            // Calling permission must be checked by LauncherAppsImpl.
-            Preconditions.checkStringNotEmpty(packageName, "packageName");
-
-            final ArrayList<ShortcutInfo> ret = new ArrayList<>(ids.size());
-            final ArraySet<String> idSet = new ArraySet<>(ids);
-            synchronized (mLock) {
-                getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
-                        .attemptToRestoreIfNeededAndSave(ShortcutService.this);
-
-                getPackageShortcutsLocked(packageName, userId).findAll(
-                        ShortcutService.this, ret,
-                        (ShortcutInfo si) -> idSet.contains(si.getId()),
-                        ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER, callingPackage, launcherUserId);
-            }
-            return ret;
-        }
-
-        @Override
         public boolean isPinnedByCaller(int launcherUserId, @NonNull String callingPackage,
                 @NonNull String packageName, @NonNull String shortcutId, int userId) {
             Preconditions.checkStringNotEmpty(packageName, "packageName");
@@ -1733,17 +1728,18 @@
         }
 
         @Override
-        public int getShortcutIconResId(int launcherUserId,
-                @NonNull String callingPackage,
-                @NonNull ShortcutInfo shortcut, int userId) {
-            Preconditions.checkNotNull(shortcut, "shortcut");
+        public int getShortcutIconResId(int launcherUserId, @NonNull String callingPackage,
+                @NonNull String packageName, @NonNull String shortcutId, int userId) {
+            Preconditions.checkNotNull(callingPackage, "callingPackage");
+            Preconditions.checkNotNull(packageName, "packageName");
+            Preconditions.checkNotNull(shortcutId, "shortcutId");
 
             synchronized (mLock) {
                 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
                         .attemptToRestoreIfNeededAndSave(ShortcutService.this);
 
                 final ShortcutInfo shortcutInfo = getPackageShortcutsLocked(
-                        shortcut.getPackageName(), userId).findShortcutById(shortcut.getId());
+                        packageName, userId).findShortcutById(shortcutId);
                 return (shortcutInfo != null && shortcutInfo.hasIconResource())
                         ? shortcutInfo.getIconResourceId() : 0;
             }
@@ -1751,16 +1747,18 @@
 
         @Override
         public ParcelFileDescriptor getShortcutIconFd(int launcherUserId,
-                @NonNull String callingPackage,
-                @NonNull ShortcutInfo shortcutIn, int userId) {
-            Preconditions.checkNotNull(shortcutIn, "shortcut");
+                @NonNull String callingPackage, @NonNull String packageName,
+                @NonNull String shortcutId, int userId) {
+            Preconditions.checkNotNull(callingPackage, "callingPackage");
+            Preconditions.checkNotNull(packageName, "packageName");
+            Preconditions.checkNotNull(shortcutId, "shortcutId");
 
             synchronized (mLock) {
                 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
                         .attemptToRestoreIfNeededAndSave(ShortcutService.this);
 
                 final ShortcutInfo shortcutInfo = getPackageShortcutsLocked(
-                        shortcutIn.getPackageName(), userId).findShortcutById(shortcutIn.getId());
+                        packageName, userId).findShortcutById(shortcutId);
                 if (shortcutInfo == null || !shortcutInfo.hasIconFile()) {
                     return null;
                 }