Merge changes from topic "shortcut-worker-thread" into ub-launcher3-master

* changes:
  fetch and update shortcut icons in background thread
  Revert "Revert "Revert "Revert "cache shourtcut image""""
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java b/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
index e40a9c2..3aa783a 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
@@ -17,6 +17,7 @@
 
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.PackageInfo;
 import android.os.LocaleList;
 import android.os.UserHandle;
 
@@ -43,6 +44,13 @@
     }
 
     /**
+     * Returns the timestamp the entry was last updated in cache.
+     */
+    default long getLastUpdatedTime(T object, PackageInfo info) {
+        return info.lastUpdateTime;
+    }
+
+    /**
      * Returns true the object should be added to mem cache; otherwise returns false.
      */
     default boolean addToMemCache() {
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java b/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
index 8224966..bcdbce5 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
@@ -171,7 +171,8 @@
                 long updateTime = c.getLong(indexLastUpdate);
                 int version = c.getInt(indexVersion);
                 T app = componentMap.remove(component);
-                if (version == info.versionCode && updateTime == info.lastUpdateTime
+                if (version == info.versionCode
+                        && updateTime == cachingLogic.getLastUpdatedTime(app, info)
                         && TextUtils.equals(c.getString(systemStateIndex),
                                 mIconCache.getIconSystemState(info.packageName))) {
 
@@ -231,7 +232,6 @@
         }
     }
 
-
     /**
      * A runnable that updates invalid icons and adds missing icons in the DB for the provided
      * LauncherActivityInfo list. Items are updated/added one at a time, so that the
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 8ebf464..0b79dd2 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -17,6 +17,7 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.launcher3.util.ShortcutUtil.fetchAndUpdateShortcutIconAsync;
 
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
@@ -482,9 +483,7 @@
                 return Pair.create(si, null);
             } else if (shortcutInfo != null) {
                 WorkspaceItemInfo itemInfo = new WorkspaceItemInfo(shortcutInfo, mContext);
-                LauncherIcons li = LauncherIcons.obtain(mContext);
-                itemInfo.applyFrom(li.createShortcutIcon(shortcutInfo));
-                li.recycle();
+                fetchAndUpdateShortcutIconAsync(mContext, itemInfo, shortcutInfo, true);
                 return Pair.create(itemInfo, shortcutInfo);
             } else if (providerInfo != null) {
                 LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 9886f53..ad01f9f 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -28,6 +28,7 @@
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ShortcutInfo;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.Process;
@@ -50,6 +51,7 @@
 import com.android.launcher3.icons.cache.CachingLogic;
 import com.android.launcher3.icons.cache.HandlerRunnable;
 import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.InstantAppResolver;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Preconditions;
@@ -65,6 +67,7 @@
 
     private final CachingLogic<ComponentWithLabel> mComponentWithLabelCachingLogic;
     private final CachingLogic<LauncherActivityInfo> mLauncherActivityInfoCachingLogic;
+    private final CachingLogic<ShortcutInfo> mShortcutCachingLogic;
 
     private final LauncherApps mLauncherApps;
     private final UserManagerCompat mUserManager;
@@ -78,6 +81,7 @@
                 inv.fillResIconDpi, inv.iconBitmapSize, true /* inMemoryCache */);
         mComponentWithLabelCachingLogic = new ComponentCachingLogic(context, false);
         mLauncherActivityInfoCachingLogic = LauncherActivityCachingLogic.newInstance(context);
+        mShortcutCachingLogic = new ShortcutCachingLogic();
         mLauncherApps = mContext.getSystemService(LauncherApps.class);
         mUserManager = UserManagerCompat.getInstance(mContext);
         mInstantAppResolver = InstantAppResolver.newInstance(mContext);
@@ -176,6 +180,14 @@
     }
 
     /**
+     * Fill in info with the icon and label for deep shortcut.
+     */
+    public synchronized CacheEntry getDeepShortcutTitleAndIcon(ShortcutInfo info) {
+        return cacheLocked(ShortcutKey.fromInfo(info).componentName, info.getUserHandle(),
+                () -> info, mShortcutCachingLogic, false, false);
+    }
+
+    /**
      * Fill in {@param info} with the icon and label. If the
      * corresponding activity is not found, it reverts to the package icon.
      */
diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java
index adc92c4..0f5d290 100644
--- a/src/com/android/launcher3/icons/LauncherIcons.java
+++ b/src/com/android/launcher3/icons/LauncherIcons.java
@@ -21,10 +21,10 @@
 import android.content.Intent;
 import android.content.pm.ShortcutInfo;
 import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
 import android.os.Process;
 
 import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.FastBitmapDrawable;
@@ -34,7 +34,6 @@
 import com.android.launcher3.R;
 import com.android.launcher3.graphics.IconShape;
 import com.android.launcher3.model.PackageItemInfo;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.util.Themes;
 
 import java.util.function.Supplier;
@@ -115,24 +114,37 @@
     }
 
     // below methods should also migrate to BaseIconFactory
-
+    @WorkerThread
     public BitmapInfo createShortcutIcon(ShortcutInfo shortcutInfo) {
         return createShortcutIcon(shortcutInfo, true /* badged */);
     }
 
+    @WorkerThread
     public BitmapInfo createShortcutIcon(ShortcutInfo shortcutInfo, boolean badged) {
         return createShortcutIcon(shortcutInfo, badged, null);
     }
 
-    public BitmapInfo createShortcutIcon(ShortcutInfo shortcutInfo,
-            boolean badged, @Nullable Supplier<ItemInfoWithIcon> fallbackIconProvider) {
-        Drawable unbadgedDrawable = DeepShortcutManager.getInstance(mContext)
-                .getShortcutIconDrawable(shortcutInfo, mFillResIconDpi);
+    @WorkerThread
+    public BitmapInfo createShortcutIcon(ShortcutInfo shortcutInfo, boolean badged,
+            @Nullable Supplier<ItemInfoWithIcon> fallbackIconProvider) {
+        return createShortcutIcon(shortcutInfo, badged, true, fallbackIconProvider);
+    }
+
+    @WorkerThread
+    public BitmapInfo createShortcutIcon(ShortcutInfo shortcutInfo, boolean badged,
+            boolean useCache, @Nullable Supplier<ItemInfoWithIcon> fallbackIconProvider) {
         IconCache cache = LauncherAppState.getInstance(mContext).getIconCache();
+        final BitmapInfo bitmapInfo;
+        if (useCache) {
+            bitmapInfo = cache.getDeepShortcutTitleAndIcon(shortcutInfo);
+        } else {
+            bitmapInfo = new BitmapInfo();
+            new ShortcutCachingLogic().loadIcon(mContext, shortcutInfo, bitmapInfo);
+        }
 
         final Bitmap unbadgedBitmap;
-        if (unbadgedDrawable != null) {
-            unbadgedBitmap = createScaledBitmapWithoutShadow(unbadgedDrawable, 0);
+        if (bitmapInfo.icon != null) {
+            unbadgedBitmap = bitmapInfo.icon;
         } else {
             if (fallbackIconProvider != null) {
                 // Fallback icons are already badged and with appropriate shadow
diff --git a/src/com/android/launcher3/icons/ShortcutCachingLogic.java b/src/com/android/launcher3/icons/ShortcutCachingLogic.java
new file mode 100644
index 0000000..5d696fd
--- /dev/null
+++ b/src/com/android/launcher3/icons/ShortcutCachingLogic.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.icons;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.ShortcutInfo;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.icons.cache.CachingLogic;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.ShortcutKey;
+
+/**
+ * Caching logic for shortcuts.
+ */
+public class ShortcutCachingLogic implements CachingLogic<ShortcutInfo> {
+
+    @Override
+    public ComponentName getComponent(ShortcutInfo info) {
+        return ShortcutKey.fromInfo(info).componentName;
+    }
+
+    @Override
+    public UserHandle getUser(ShortcutInfo info) {
+        return info.getUserHandle();
+    }
+
+    @Override
+    public CharSequence getLabel(ShortcutInfo info) {
+        return info.getShortLabel();
+    }
+
+    @Override
+    public void loadIcon(Context context, ShortcutInfo info, BitmapInfo target) {
+        LauncherIcons li = LauncherIcons.obtain(context);
+        Drawable unbadgedDrawable = DeepShortcutManager.getInstance(context)
+                .getShortcutIconDrawable(info, LauncherAppState.getIDP(context).fillResIconDpi);
+        if (unbadgedDrawable != null) {
+            target.icon = li.createScaledBitmapWithoutShadow(unbadgedDrawable, 0);
+        }
+        li.recycle();
+    }
+
+    @Override
+    public long getLastUpdatedTime(ShortcutInfo shortcutInfo, PackageInfo info) {
+        if (shortcutInfo == null) return info.lastUpdateTime;
+        return Math.max(shortcutInfo.getLastChangedTimestamp(), info.lastUpdateTime);
+    }
+
+    @Override
+    public boolean addToMemCache() {
+        return false;
+    }
+}
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 497b99d..3673102 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -63,6 +63,7 @@
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.LauncherActivityCachingLogic;
 import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.icons.ShortcutCachingLogic;
 import com.android.launcher3.icons.cache.IconCacheUpdateHandler;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.pm.PackageInstallInfo;
@@ -182,7 +183,8 @@
         TraceHelper.INSTANCE.beginSection(TAG);
         TimingLogger logger = new TimingLogger(TAG, "run");
         try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
-            loadWorkspace();
+            List<ShortcutInfo> allShortcuts = new ArrayList<>();
+            loadWorkspace(allShortcuts);
             logger.addSplit("loadWorkspace");
 
             verifyNotStopped();
@@ -214,19 +216,29 @@
                     mApp.getModel()::onPackageIconsUpdated);
             logger.addSplit("update icon cache");
 
+            verifyNotStopped();
+            logger.addSplit("save shortcuts in icon cache");
+            updateHandler.updateIcons(allShortcuts, new ShortcutCachingLogic(),
+                    mApp.getModel()::onPackageIconsUpdated);
+
             // Take a break
             waitForIdle();
             logger.addSplit("step 2 complete");
             verifyNotStopped();
 
             // third step
-            loadDeepShortcuts();
+            List<ShortcutInfo> allDeepShortcuts = loadDeepShortcuts();
             logger.addSplit("loadDeepShortcuts");
 
             verifyNotStopped();
             mResults.bindDeepShortcuts();
             logger.addSplit("bindDeepShortcuts");
 
+            verifyNotStopped();
+            logger.addSplit("save deep shortcuts in icon cache");
+            updateHandler.updateIcons(allDeepShortcuts,
+                    new ShortcutCachingLogic(), (pkgs, user) -> { });
+
             // Take a break
             waitForIdle();
             logger.addSplit("step 3 complete");
@@ -268,7 +280,7 @@
         this.notify();
     }
 
-    private void loadWorkspace() {
+    private void loadWorkspace(List<ShortcutInfo> allDeepShortcuts) {
         final Context context = mApp.getContext();
         final ContentResolver contentResolver = context.getContentResolver();
         final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
@@ -523,6 +535,7 @@
                                         info.runtimeStatusFlags |= FLAG_DISABLED_SUSPENDED;
                                     }
                                     intent = info.intent;
+                                    allDeepShortcuts.add(pinnedShortcut);
                                 } else {
                                     // Create a shortcut info in disabled mode for now.
                                     info = c.loadSimpleWorkspaceItem();
@@ -871,7 +884,8 @@
         return allActivityList;
     }
 
-    private void loadDeepShortcuts() {
+    private List<ShortcutInfo> loadDeepShortcuts() {
+        List<ShortcutInfo> allShortcuts = new ArrayList<>();
         mBgDataModel.deepShortcutMap.clear();
         mBgDataModel.hasShortcutHostPermission = mShortcutManager.hasHostPermission();
         if (mBgDataModel.hasShortcutHostPermission) {
@@ -879,10 +893,12 @@
                 if (mUserManager.isUserUnlocked(user)) {
                     List<ShortcutInfo> shortcuts =
                             mShortcutManager.queryForAllShortcuts(user);
+                    allShortcuts.addAll(shortcuts);
                     mBgDataModel.updateDeepShortcutCounts(null, user, shortcuts);
                 }
             }
         }
+        return allShortcuts;
     }
 
     public static boolean isValidProvider(AppWidgetProviderInfo provider) {
diff --git a/src/com/android/launcher3/pm/PinRequestHelper.java b/src/com/android/launcher3/pm/PinRequestHelper.java
index 68ea6c4..5b6b56d 100644
--- a/src/com/android/launcher3/pm/PinRequestHelper.java
+++ b/src/com/android/launcher3/pm/PinRequestHelper.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.pm;
 
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.launcher3.util.ShortcutUtil.fetchAndUpdateShortcutIconAsync;
 
 import android.annotation.TargetApi;
 import android.content.Context;
@@ -29,9 +30,7 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.icons.LauncherIcons;
 
 public class PinRequestHelper {
 
@@ -81,11 +80,7 @@
             ShortcutInfo si = request.getShortcutInfo();
             WorkspaceItemInfo info = new WorkspaceItemInfo(si, context);
             // Apply the unbadged icon and fetch the actual icon asynchronously.
-            LauncherIcons li = LauncherIcons.obtain(context);
-            info.applyFrom(li.createShortcutIcon(si, false /* badged */));
-            li.recycle();
-            LauncherAppState.getInstance(context).getModel()
-                    .updateAndBindWorkspaceItem(info, si);
+            fetchAndUpdateShortcutIconAsync(context, info, si, false);
             return info;
         } else {
             return null;
diff --git a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
index ee97641..408ced2 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
@@ -26,6 +26,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.graphics.DragPreviewProvider;
+import com.android.launcher3.icons.BitmapRenderer;
 
 /**
  * Extension of {@link DragPreviewProvider} which generates bitmaps scaled to the default icon size.
@@ -39,22 +40,22 @@
         mPositionShift = shift;
     }
 
+    @Override
     public Bitmap createDragBitmap() {
+        int size = Launcher.getLauncher(mView.getContext()).getDeviceProfile().iconSizePx;
+        return BitmapRenderer.createHardwareBitmap(
+                size + blurSizeOutline,
+                size + blurSizeOutline,
+                (c) -> drawDragViewOnBackground(c, size));
+    }
+
+    private void drawDragViewOnBackground(Canvas canvas, float size) {
         Drawable d = mView.getBackground();
         Rect bounds = getDrawableBounds(d);
-
-        int size = Launcher.getLauncher(mView.getContext()).getDeviceProfile().iconSizePx;
-        final Bitmap b = Bitmap.createBitmap(
-                size + blurSizeOutline,
-                size + blurSizeOutline,
-                Bitmap.Config.ARGB_8888);
-
-        Canvas canvas = new Canvas(b);
         canvas.translate(blurSizeOutline / 2, blurSizeOutline / 2);
-        canvas.scale(((float) size) / bounds.width(), ((float) size) / bounds.height(), 0, 0);
+        canvas.scale(size / bounds.width(), size / bounds.height(), 0, 0);
         canvas.translate(bounds.left, bounds.top);
         d.draw(canvas);
-        return b;
     }
 
     @Override
diff --git a/src/com/android/launcher3/util/ShortcutUtil.java b/src/com/android/launcher3/util/ShortcutUtil.java
index 49c97da..a69cd6c 100644
--- a/src/com/android/launcher3/util/ShortcutUtil.java
+++ b/src/com/android/launcher3/util/ShortcutUtil.java
@@ -15,10 +15,20 @@
  */
 package com.android.launcher3.util;
 
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
+import android.content.Context;
+import android.content.pm.ShortcutInfo;
+
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.shortcuts.ShortcutKey;
 
@@ -61,6 +71,26 @@
                 && info instanceof WorkspaceItemInfo;
     }
 
+    /**
+     * Fetch the shortcut icon in background, then update the UI.
+     */
+    public static void fetchAndUpdateShortcutIconAsync(
+            @NonNull Context context, @NonNull WorkspaceItemInfo info, @NonNull ShortcutInfo si,
+            boolean badged) {
+        if (info.iconBitmap == null) {
+            // use low res icon as placeholder while the actual icon is being fetched.
+            info.iconBitmap = BitmapInfo.LOW_RES_ICON;
+            info.iconColor = Themes.getColorAccent(context);
+        }
+        MODEL_EXECUTOR.execute(() -> {
+            LauncherIcons li = LauncherIcons.obtain(context);
+            BitmapInfo bitmapInfo = li.createShortcutIcon(si, badged, true, null);
+            info.applyFrom(bitmapInfo);
+            li.recycle();
+            LauncherAppState.getInstance(context).getModel().updateAndBindWorkspaceItem(info, si);
+        });
+    }
+
     private static boolean isActive(ItemInfo info) {
         boolean isLoading = info instanceof WorkspaceItemInfo
                 && ((WorkspaceItemInfo) info).hasPromiseIconUi();