Using a placeholder icon shape instead of low-res/blurry icon

Bug: 111142970
Change-Id: I867224464ae9c026f4dcb5256ef14fc39c8e751d
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java
index 2482691..45859ca 100644
--- a/src/com/android/launcher3/AllAppsList.java
+++ b/src/com/android/launcher3/AllAppsList.java
@@ -92,7 +92,7 @@
         // only if not yet installed
         if (applicationInfo == null) {
             PromiseAppInfo info = new PromiseAppInfo(installInfo);
-            mIconCache.getTitleAndIcon(info, info.usingLowResIcon);
+            mIconCache.getTitleAndIcon(info, info.usingLowResIcon());
             data.add(info);
             added.add(info);
         }
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 230ea4f..5d40143 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -256,7 +256,8 @@
     }
 
     private void applyIconAndLabel(ItemInfoWithIcon info) {
-        FastBitmapDrawable iconDrawable = DrawableFactory.get(getContext()).newIcon(info);
+        FastBitmapDrawable iconDrawable = DrawableFactory.INSTANCE.get(getContext())
+                .newIcon(getContext(), info);
         mBadgeColor = IconPalette.getMutedColor(info.iconColor, 0.54f);
 
         setIcon(iconDrawable);
@@ -527,8 +528,8 @@
                     preloadDrawable = (PreloadIconDrawable) mIcon;
                     preloadDrawable.setLevel(progressLevel);
                 } else {
-                    preloadDrawable = DrawableFactory.get(getContext())
-                            .newPendingIcon(info, getContext());
+                    preloadDrawable = DrawableFactory.INSTANCE.get(getContext())
+                            .newPendingIcon(getContext(), info);
                     preloadDrawable.setLevel(progressLevel);
                     setIcon(preloadDrawable);
                 }
@@ -639,7 +640,7 @@
         }
         if (getTag() instanceof ItemInfoWithIcon) {
             ItemInfoWithIcon info = (ItemInfoWithIcon) getTag();
-            if (info.usingLowResIcon) {
+            if (info.usingLowResIcon()) {
                 mIconLoadRequest = LauncherAppState.getInstance(getContext()).getIconCache()
                         .updateIconInBackground(BubbleTextView.this, info);
             }
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index abd5538..61fb3e3 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.graphics.BitmapInfo.LOW_RES_ICON;
+
 import android.content.ComponentName;
 import android.content.ContentValues;
 import android.content.Context;
@@ -79,14 +81,15 @@
     private static final boolean DEBUG = false;
     private static final boolean DEBUG_IGNORE_CACHE = false;
 
-    private static final int LOW_RES_SCALE_FACTOR = 5;
-
     @Thunk static final Object ICON_UPDATE_TOKEN = new Object();
 
     public static class CacheEntry extends BitmapInfo {
         public CharSequence title = "";
         public CharSequence contentDescription = "";
-        public boolean isLowResIcon;
+
+        public boolean isLowRes() {
+            return LOW_RES_ICON == icon;
+        }
     }
 
     private final HashMap<UserHandle, BitmapInfo> mDefaultIcons = new HashMap<>();
@@ -105,8 +108,7 @@
 
     @Thunk final Handler mWorkerHandler;
 
-    private final BitmapFactory.Options mLowResOptions;
-    private final BitmapFactory.Options mHighResOptions;
+    private final BitmapFactory.Options mDecodeOptions;
 
     private int mPendingIconRequestCount = 0;
 
@@ -122,16 +124,11 @@
         mIconProvider = IconProvider.newInstance(context);
         mWorkerHandler = new Handler(LauncherModel.getWorkerLooper());
 
-        mLowResOptions = new BitmapFactory.Options();
-        // Always prefer RGB_565 config for low res. If the bitmap has transparency, it will
-        // automatically be loaded as ALPHA_8888.
-        mLowResOptions.inPreferredConfig = Bitmap.Config.RGB_565;
-
         if (BitmapRenderer.USE_HARDWARE_BITMAP) {
-            mHighResOptions = new BitmapFactory.Options();
-            mHighResOptions.inPreferredConfig = Bitmap.Config.HARDWARE;
+            mDecodeOptions = new BitmapFactory.Options();
+            mDecodeOptions.inPreferredConfig = Bitmap.Config.HARDWARE;
         } else {
-            mHighResOptions = null;
+            mDecodeOptions = null;
         }
     }
 
@@ -374,7 +371,7 @@
         if (!replaceExisting) {
             entry = mCache.get(key);
             // We can't reuse the entry if the high-res icon is not present.
-            if (entry == null || entry.isLowResIcon || entry.icon == null) {
+            if (entry == null || entry.icon == null || entry.isLowRes()) {
                 entry = null;
             }
         }
@@ -389,8 +386,7 @@
         entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, app.getUser());
         mCache.put(key, entry);
 
-        Bitmap lowResIcon = generateLowResIcon(entry.icon);
-        ContentValues values = newContentValues(entry.icon, lowResIcon, entry.color,
+        ContentValues values = newContentValues(entry.icon, entry.color,
                 entry.title.toString(), app.getApplicationInfo().packageName);
         addIconToDB(values, app.getComponentName(), info, userSerial);
     }
@@ -451,7 +447,7 @@
     public synchronized void updateTitleAndIcon(AppInfo application) {
         CacheEntry entry = cacheLocked(application.componentName,
                 Provider.<LauncherActivityInfo>of(null),
-                application.user, false, application.usingLowResIcon);
+                application.user, false, application.usingLowResIcon());
         if (entry.icon != null && !isDefaultIcon(entry.icon, application.user)) {
             applyCacheEntry(entry, application);
         }
@@ -477,7 +473,6 @@
             getDefaultIcon(info.user).applyTo(info);
             info.title = "";
             info.contentDescription = "";
-            info.usingLowResIcon = false;
         } else {
             Intent intent = info.getIntent();
             getTitleAndIcon(info, () -> mLauncherApps.resolveActivity(intent, info.user),
@@ -510,7 +505,6 @@
     private void applyCacheEntry(CacheEntry entry, ItemInfoWithIcon info) {
         info.title = Utilities.trim(entry.title);
         info.contentDescription = entry.contentDescription;
-        info.usingLowResIcon = entry.isLowResIcon;
         ((entry.icon == null) ? getDefaultIcon(info.user) : entry).applyTo(info);
     }
 
@@ -536,7 +530,7 @@
         Preconditions.assertWorkerThread();
         ComponentKey cacheKey = new ComponentKey(componentName, user);
         CacheEntry entry = mCache.get(cacheKey);
-        if (entry == null || (entry.isLowResIcon && !useLowResIcon)) {
+        if (entry == null || (entry.isLowRes() && !useLowResIcon)) {
             entry = new CacheEntry();
             mCache.put(cacheKey, entry);
 
@@ -635,7 +629,7 @@
         ComponentKey cacheKey = getPackageKey(packageName, user);
         CacheEntry entry = mCache.get(cacheKey);
 
-        if (entry == null || (entry.isLowResIcon && !useLowResIcon)) {
+        if (entry == null || (entry.isLowRes() && !useLowResIcon)) {
             entry = new CacheEntry();
             boolean entryUpdated = true;
 
@@ -658,16 +652,14 @@
                             mInstantAppResolver.isInstantApp(appInfo));
                     li.recycle();
 
-                    Bitmap lowResIcon =  generateLowResIcon(iconInfo.icon);
                     entry.title = appInfo.loadLabel(mPackageManager);
                     entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
-                    entry.icon = useLowResIcon ? lowResIcon : iconInfo.icon;
+                    entry.icon = useLowResIcon ? LOW_RES_ICON : iconInfo.icon;
                     entry.color = iconInfo.color;
-                    entry.isLowResIcon = useLowResIcon;
 
                     // Add the icon in the DB here, since these do not get written during
                     // package updates.
-                    ContentValues values = newContentValues(iconInfo.icon, lowResIcon, entry.color,
+                    ContentValues values = newContentValues(iconInfo.icon, entry.color,
                             entry.title.toString(), packageName);
                     addIconToDB(values, cacheKey.componentName, info,
                             mUserManager.getSerialNumberForUser(user));
@@ -690,17 +682,15 @@
         Cursor c = null;
         try {
             c = mIconDb.query(
-                new String[]{lowRes ? IconDB.COLUMN_ICON_LOW_RES : IconDB.COLUMN_ICON,
-                        IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL},
-                IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?",
-                new String[]{cacheKey.componentName.flattenToString(),
-                        Long.toString(mUserManager.getSerialNumberForUser(cacheKey.user))});
+                    lowRes ? IconDB.COLUMNS_LOW_RES : IconDB.COLUMNS_HIGH_RES,
+                    IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?",
+                    new String[]{
+                            cacheKey.componentName.flattenToString(),
+                            Long.toString(mUserManager.getSerialNumberForUser(cacheKey.user))});
             if (c.moveToNext()) {
-                entry.icon = loadIconNoResize(c, 0, lowRes ? mLowResOptions : mHighResOptions);
                 // Set the alpha to be 255, so that we never have a wrong color
-                entry.color = ColorUtils.setAlphaComponent(c.getInt(1), 255);
-                entry.isLowResIcon = lowRes;
-                entry.title = c.getString(2);
+                entry.color = ColorUtils.setAlphaComponent(c.getInt(0), 255);
+                entry.title = c.getString(1);
                 if (entry.title == null) {
                     entry.title = "";
                     entry.contentDescription = "";
@@ -708,6 +698,16 @@
                     entry.contentDescription = mUserManager.getBadgedLabelForUser(
                             entry.title, cacheKey.user);
                 }
+
+                if (lowRes) {
+                    entry.icon = LOW_RES_ICON;
+                } else {
+                    byte[] data = c.getBlob(2);
+                    try {
+                        entry.icon = BitmapFactory.decodeByteArray(data, 0, data.length,
+                                mDecodeOptions);
+                    } catch (Exception e) { }
+                }
                 return true;
             }
         } catch (SQLiteException e) {
@@ -803,7 +803,7 @@
     }
 
     private static final class IconDB extends SQLiteCacheHelper {
-        private final static int RELEASE_VERSION = 24;
+        private final static int RELEASE_VERSION = 25;
 
         private final static String TABLE_NAME = "icons";
         private final static String COLUMN_ROWID = "rowid";
@@ -812,11 +812,15 @@
         private final static String COLUMN_LAST_UPDATED = "lastUpdated";
         private final static String COLUMN_VERSION = "version";
         private final static String COLUMN_ICON = "icon";
-        private final static String COLUMN_ICON_LOW_RES = "icon_low_res";
         private final static String COLUMN_ICON_COLOR = "icon_color";
         private final static String COLUMN_LABEL = "label";
         private final static String COLUMN_SYSTEM_STATE = "system_state";
 
+        private final static String[] COLUMNS_HIGH_RES = new String[] {
+                IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL, IconDB.COLUMN_ICON };
+        private final static String[] COLUMNS_LOW_RES = new String[] {
+                IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL };
+
         public IconDB(Context context, int iconPixelSize) {
             super(context, LauncherFiles.APP_ICONS_DB,
                     (RELEASE_VERSION << 16) + iconPixelSize,
@@ -831,7 +835,6 @@
                     COLUMN_LAST_UPDATED + " INTEGER NOT NULL DEFAULT 0, " +
                     COLUMN_VERSION + " INTEGER NOT NULL DEFAULT 0, " +
                     COLUMN_ICON + " BLOB, " +
-                    COLUMN_ICON_LOW_RES + " BLOB, " +
                     COLUMN_ICON_COLOR + " INTEGER NOT NULL DEFAULT 0, " +
                     COLUMN_LABEL + " TEXT, " +
                     COLUMN_SYSTEM_STATE + " TEXT, " +
@@ -840,11 +843,10 @@
         }
     }
 
-    private ContentValues newContentValues(Bitmap icon, Bitmap lowResIcon, int iconColor,
-            String label, String packageName) {
+    private ContentValues newContentValues(Bitmap icon, int iconColor, String label,
+            String packageName) {
         ContentValues values = new ContentValues();
         values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(icon));
-        values.put(IconDB.COLUMN_ICON_LOW_RES, Utilities.flattenBitmap(lowResIcon));
         values.put(IconDB.COLUMN_ICON_COLOR, iconColor);
 
         values.put(IconDB.COLUMN_LABEL, label);
@@ -854,24 +856,6 @@
     }
 
     /**
-     * Generates a new low-res icon given a high-res icon.
-     */
-    private Bitmap generateLowResIcon(Bitmap icon) {
-        return Bitmap.createScaledBitmap(icon,
-                icon.getWidth() / LOW_RES_SCALE_FACTOR,
-                icon.getHeight() / LOW_RES_SCALE_FACTOR, true);
-    }
-
-    private static Bitmap loadIconNoResize(Cursor c, int iconIndex, BitmapFactory.Options options) {
-        byte[] data = c.getBlob(iconIndex);
-        try {
-            return BitmapFactory.decodeByteArray(data, 0, data.length, options);
-        } catch (Exception e) {
-            return null;
-        }
-    }
-
-    /**
      * Interface for receiving itemInfo with high-res icon.
      */
     public interface ItemInfoUpdateReceiver {
diff --git a/src/com/android/launcher3/ItemInfoWithIcon.java b/src/com/android/launcher3/ItemInfoWithIcon.java
index 4677d31..2ceb0dd 100644
--- a/src/com/android/launcher3/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/ItemInfoWithIcon.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.graphics.BitmapInfo.LOW_RES_ICON;
+
 import android.graphics.Bitmap;
 
 /**
@@ -34,11 +36,6 @@
     public int iconColor;
 
     /**
-     * Indicates whether we're using a low res icon
-     */
-    public boolean usingLowResIcon;
-
-    /**
      * Indicates that the icon is disabled due to safe mode restrictions.
      */
     public static final int FLAG_DISABLED_SAFEMODE = 1 << 0;
@@ -107,7 +104,6 @@
         super(info);
         iconBitmap = info.iconBitmap;
         iconColor = info.iconColor;
-        usingLowResIcon = info.usingLowResIcon;
         runtimeStatusFlags = info.runtimeStatusFlags;
     }
 
@@ -115,4 +111,11 @@
     public boolean isDisabled() {
         return (runtimeStatusFlags & FLAG_DISABLED_MASK) != 0;
     }
+
+    /**
+     * Indicates whether we're using a low res icon
+     */
+    public boolean usingLowResIcon() {
+        return iconBitmap == LOW_RES_ICON;
+    }
 }
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index 8588c7a..a84bfd5 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -125,7 +125,7 @@
                 .put(LauncherSettings.BaseLauncherColumns.INTENT, getIntent())
                 .put(LauncherSettings.Favorites.RESTORED, status);
 
-        if (!usingLowResIcon) {
+        if (!usingLowResIcon()) {
             writer.putIcon(iconBitmap, user);
         }
         if (iconResource != null) {
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index adc4a72..fa07d4d 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -152,8 +152,7 @@
 
         if (mApps.hasNoFilteredResults()) {
             if (mEmptySearchBackground == null) {
-                mEmptySearchBackground = DrawableFactory.get(getContext())
-                        .getAllAppsBackground(getContext());
+                mEmptySearchBackground = new AllAppsBackgroundDrawable(getContext());
                 mEmptySearchBackground.setAlpha(0);
                 mEmptySearchBackground.setCallback(this);
                 updateEmptySearchBackgroundBounds();
diff --git a/src/com/android/launcher3/graphics/BitmapInfo.java b/src/com/android/launcher3/graphics/BitmapInfo.java
index c905a78..69c0684 100644
--- a/src/com/android/launcher3/graphics/BitmapInfo.java
+++ b/src/com/android/launcher3/graphics/BitmapInfo.java
@@ -16,11 +16,14 @@
 package com.android.launcher3.graphics;
 
 import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
 
 import com.android.launcher3.ItemInfoWithIcon;
 
 public class BitmapInfo {
 
+    public static final Bitmap LOW_RES_ICON = Bitmap.createBitmap(1, 1, Config.ALPHA_8);
+
     public Bitmap icon;
     public int color;
 
diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java
index 5bc81e6..8377adf 100644
--- a/src/com/android/launcher3/graphics/DrawableFactory.java
+++ b/src/com/android/launcher3/graphics/DrawableFactory.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.graphics;
 
+import static com.android.launcher3.graphics.BitmapInfo.LOW_RES_ICON;
+
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.Resources;
@@ -24,18 +26,18 @@
 import android.graphics.Color;
 import android.graphics.Path;
 import android.graphics.Rect;
+import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Process;
 import android.os.UserHandle;
 import android.util.ArrayMap;
-import android.util.Log;
 
 import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.allapps.AllAppsBackgroundDrawable;
+import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.ResourceBasedOverride;
 
 import androidx.annotation.UiThread;
@@ -45,71 +47,65 @@
  */
 public class DrawableFactory implements ResourceBasedOverride {
 
-    private static final String TAG = "DrawableFactory";
+    public static final MainThreadInitializedObject<DrawableFactory> INSTANCE =
+            new MainThreadInitializedObject<>(c -> {
+                DrawableFactory factory = Overrides.getObject(DrawableFactory.class,
+                        c.getApplicationContext(), R.string.drawable_factory_class);
+                factory.mContext = c;
+                return factory;
+            });
 
-    private static DrawableFactory sInstance;
-    private static final Object LOCK = new Object();
 
+    private Context mContext;
     private Path mPreloadProgressPath;
 
-    public static DrawableFactory get(Context context) {
-        synchronized (LOCK) {
-            if (sInstance == null) {
-                sInstance = Overrides.getObject(DrawableFactory.class,
-                        context.getApplicationContext(), R.string.drawable_factory_class);
-            }
-            return sInstance;
-        }
-    }
-
     protected final UserHandle mMyUser = Process.myUserHandle();
     protected final ArrayMap<UserHandle, Bitmap> mUserBadges = new ArrayMap<>();
 
     /**
      * Returns a FastBitmapDrawable with the icon.
      */
-    public FastBitmapDrawable newIcon(ItemInfoWithIcon info) {
-        FastBitmapDrawable drawable = new FastBitmapDrawable(info);
+    public FastBitmapDrawable newIcon(Context context, ItemInfoWithIcon info) {
+        FastBitmapDrawable drawable = info.iconBitmap == LOW_RES_ICON
+                ? new PlaceHolderIconDrawable(info, getPreloadProgressPath(), context)
+                : new FastBitmapDrawable(info);
         drawable.setIsDisabled(info.isDisabled());
         return drawable;
     }
 
-    public FastBitmapDrawable newIcon(BitmapInfo info, ActivityInfo target) {
-        return new FastBitmapDrawable(info);
+    public FastBitmapDrawable newIcon(Context context, BitmapInfo info, ActivityInfo target) {
+        return info.icon == LOW_RES_ICON
+                ? new PlaceHolderIconDrawable(info, getPreloadProgressPath(), context)
+                : new FastBitmapDrawable(info);
     }
 
     /**
      * Returns a FastBitmapDrawable with the icon.
      */
-    public PreloadIconDrawable newPendingIcon(ItemInfoWithIcon info, Context context) {
-        if (mPreloadProgressPath == null) {
-            mPreloadProgressPath = getPreloadProgressPath(context);
-        }
-        return new PreloadIconDrawable(info, mPreloadProgressPath, context);
+    public PreloadIconDrawable newPendingIcon(Context context, ItemInfoWithIcon info) {
+        return new PreloadIconDrawable(info, getPreloadProgressPath(), context);
     }
 
-    protected Path getPreloadProgressPath(Context context) {
+    protected Path getPreloadProgressPath() {
+        if (mPreloadProgressPath != null) {
+            return mPreloadProgressPath;
+        }
         if (Utilities.ATLEAST_OREO) {
-            try {
-                // Try to load the path from Mask Icon
-                Drawable icon = context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper);
-                icon.setBounds(0, 0,
-                        PreloadIconDrawable.PATH_SIZE, PreloadIconDrawable.PATH_SIZE);
-                return (Path) icon.getClass().getMethod("getIconMask").invoke(icon);
-            } catch (Exception e) {
-                Log.e(TAG, "Error loading mask icon", e);
-            }
+            // Load the path from Mask Icon
+            AdaptiveIconDrawable icon = (AdaptiveIconDrawable)
+                    mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper);
+            icon.setBounds(0, 0,
+                    PreloadIconDrawable.PATH_SIZE, PreloadIconDrawable.PATH_SIZE);
+            mPreloadProgressPath = icon.getIconMask();
+        } else {
+
+            // Create a circle static from top center and going clockwise.
+            Path p = new Path();
+            p.moveTo(PreloadIconDrawable.PATH_SIZE / 2, 0);
+            p.addArc(0, 0, PreloadIconDrawable.PATH_SIZE, PreloadIconDrawable.PATH_SIZE, -90, 360);
+            mPreloadProgressPath = p;
         }
-
-        // Create a circle static from top center and going clockwise.
-        Path p = new Path();
-        p.moveTo(PreloadIconDrawable.PATH_SIZE / 2, 0);
-        p.addArc(0, 0, PreloadIconDrawable.PATH_SIZE, PreloadIconDrawable.PATH_SIZE, -90, 360);
-        return p;
-    }
-
-    public AllAppsBackgroundDrawable getAllAppsBackground(Context context) {
-        return new AllAppsBackgroundDrawable(context);
+        return mPreloadProgressPath;
     }
 
     /**
diff --git a/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java b/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java
new file mode 100644
index 0000000..18efd47
--- /dev/null
+++ b/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2018 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.graphics;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Path;
+import android.graphics.Rect;
+
+import com.android.launcher3.FastBitmapDrawable;
+import com.android.launcher3.ItemInfoWithIcon;
+import com.android.launcher3.R;
+import com.android.launcher3.util.Themes;
+
+/**
+ * Subclass which draws a placeholder icon when the actual icon is not yet loaded
+ */
+public class PlaceHolderIconDrawable extends FastBitmapDrawable {
+
+    // Path in [0, 100] bounds.
+    private final Path mProgressPath;
+
+    public PlaceHolderIconDrawable(BitmapInfo info, Path progressPath, Context context) {
+        this(info.icon, info.color, progressPath, context);
+    }
+
+    public PlaceHolderIconDrawable(ItemInfoWithIcon info, Path progressPath, Context context) {
+        this(info.iconBitmap, info.iconColor, progressPath, context);
+    }
+
+    protected PlaceHolderIconDrawable(Bitmap b, int iconColor, Path progressPath, Context context) {
+        super(b, iconColor);
+
+        mProgressPath = progressPath;
+        mPaint.setColor(Themes.getAttrColor(context, R.attr.loadingIconColor));
+    }
+
+    @Override
+    protected void drawInternal(Canvas canvas, Rect bounds) {
+        int saveCount = canvas.save();
+        canvas.translate(bounds.left, bounds.top);
+        canvas.scale(bounds.width() / 100f, bounds.height() / 100f);
+        canvas.drawPath(mProgressPath, mPaint);
+        canvas.restoreToCount(saveCount);
+    }
+}
diff --git a/src/com/android/launcher3/model/CacheDataUpdatedTask.java b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
index 0139bd9..54c0542 100644
--- a/src/com/android/launcher3/model/CacheDataUpdatedTask.java
+++ b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
@@ -64,7 +64,7 @@
                     if (si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
                             && isValidShortcut(si) && cn != null
                             && mPackages.contains(cn.getPackageName())) {
-                        iconCache.getTitleAndIcon(si, si.usingLowResIcon);
+                        iconCache.getTitleAndIcon(si, si.usingLowResIcon());
                         updatedShortcuts.add(si);
                     }
                 }
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index e82c8f1..85faf4f 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -742,7 +742,7 @@
 
                 int numItemsInPreview = 0;
                 for (ShortcutInfo info : folder.contents) {
-                    if (info.usingLowResIcon
+                    if (info.usingLowResIcon()
                             && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
                             && verifier.isItemInPreview(info.rank)) {
                         mIconCache.getTitleAndIcon(info, false);
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 977dcd7..ccb1f09 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -261,7 +261,7 @@
 
                             if (isNewApkAvailable &&
                                     si.itemType == Favorites.ITEM_TYPE_APPLICATION) {
-                                iconCache.getTitleAndIcon(si, si.usingLowResIcon);
+                                iconCache.getTitleAndIcon(si, si.usingLowResIcon());
                                 infoUpdated = true;
                             }
 
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index 961799d..29b4b0b 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -17,7 +17,6 @@
 package com.android.launcher3.widget;
 
 import android.content.Context;
-import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.PorterDuff;
@@ -37,15 +36,12 @@
 import com.android.launcher3.IconCache;
 import com.android.launcher3.IconCache.ItemInfoUpdateReceiver;
 import com.android.launcher3.ItemInfoWithIcon;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.graphics.DrawableFactory;
 import com.android.launcher3.model.PackageItemInfo;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.util.Themes;
-import com.android.launcher3.widget.LauncherAppWidgetHostView;
 
 public class PendingAppWidgetHostView extends LauncherAppWidgetHostView
         implements OnClickListener, ItemInfoUpdateReceiver {
@@ -137,19 +133,19 @@
             //   1) App icon in the center
             //   2) Preload icon in the center
             //   3) Setup icon in the center and app icon in the top right corner.
-            DrawableFactory drawableFactory = DrawableFactory.get(getContext());
+            DrawableFactory drawableFactory = DrawableFactory.INSTANCE.get(getContext());
             if (mDisabledForSafeMode) {
-                FastBitmapDrawable disabledIcon = drawableFactory.newIcon(info);
+                FastBitmapDrawable disabledIcon = drawableFactory.newIcon(getContext(), info);
                 disabledIcon.setIsDisabled(true);
                 mCenterDrawable = disabledIcon;
                 mSettingIconDrawable = null;
             } else if (isReadyForClickSetup()) {
-                mCenterDrawable = drawableFactory.newIcon(info);
+                mCenterDrawable = drawableFactory.newIcon(getContext(), info);
                 mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate();
                 updateSettingColor(info.iconColor);
             } else {
-                mCenterDrawable = DrawableFactory.get(getContext())
-                        .newPendingIcon(info, getContext());
+                mCenterDrawable = DrawableFactory.INSTANCE.get(getContext())
+                        .newPendingIcon(getContext(), info);
                 mSettingIconDrawable = null;
                 applyState();
             }
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 2ba55ab..66af43a 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -179,8 +179,8 @@
             return;
         }
         if (bitmap != null) {
-            mWidgetImage.setBitmap(bitmap,
-                    DrawableFactory.get(getContext()).getBadgeForUser(mItem.user, getContext()));
+            mWidgetImage.setBitmap(bitmap, DrawableFactory.INSTANCE.get(getContext())
+                    .getBadgeForUser(mItem.user, getContext()));
             if (mAnimatePreview) {
                 mWidgetImage.setAlpha(0f);
                 ViewPropertyAnimator anim = mWidgetImage.animate();