Adding support for dynamic calendar and clock icons

Change-Id: Icdba34340a27a4f6dff7310d0bf9fd29aef1330c
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index e8aa83f..ff5f33d 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.FastBitmapDrawable.newIcon;
+import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
 
 import android.animation.Animator;
@@ -45,7 +47,6 @@
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.dot.DotInfo;
 import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.graphics.DrawableFactory;
 import com.android.launcher3.graphics.IconPalette;
 import com.android.launcher3.graphics.IconShape;
 import com.android.launcher3.graphics.PreloadIconDrawable;
@@ -287,8 +288,7 @@
     }
 
     private void applyIconAndLabel(ItemInfoWithIcon info) {
-        FastBitmapDrawable iconDrawable = DrawableFactory.INSTANCE.get(getContext())
-                .newIcon(getContext(), info);
+        FastBitmapDrawable iconDrawable = newIcon(getContext(), info);
         mDotParams.color = IconPalette.getMutedColor(info.bitmap.color, 0.54f);
 
         setIcon(iconDrawable);
@@ -567,8 +567,7 @@
                     preloadDrawable = (PreloadIconDrawable) mIcon;
                     preloadDrawable.setLevel(progressLevel);
                 } else {
-                    preloadDrawable = DrawableFactory.INSTANCE.get(getContext())
-                            .newPendingIcon(getContext(), info);
+                    preloadDrawable = newPendingIcon(getContext(), info);
                     preloadDrawable.setLevel(progressLevel);
                     setIcon(preloadDrawable);
                 }
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index 2e57f71..5091684 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -20,6 +20,7 @@
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 
 import android.animation.ObjectAnimator;
+import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -35,6 +36,7 @@
 import android.util.Property;
 import android.util.SparseArray;
 
+import com.android.launcher3.graphics.PlaceHolderIconDrawable;
 import com.android.launcher3.icons.BitmapInfo;
 
 public class FastBitmapDrawable extends Drawable {
@@ -361,7 +363,7 @@
         }
 
         @Override
-        public Drawable newDrawable() {
+        public FastBitmapDrawable newDrawable() {
             return new FastBitmapDrawable(mBitmap, mIconColor, mIsDisabled);
         }
 
@@ -370,4 +372,37 @@
             return 0;
         }
     }
+
+    /**
+     * Interface to be implemented by custom {@link BitmapInfo} to handle drawable construction
+     */
+    public interface Factory {
+
+        /**
+         * Called to create a new drawable
+         */
+        FastBitmapDrawable newDrawable();
+    }
+
+    /**
+     * Returns a FastBitmapDrawable with the icon.
+     */
+    public static FastBitmapDrawable newIcon(Context context, ItemInfoWithIcon info) {
+        FastBitmapDrawable drawable = newIcon(context, info.bitmap);
+        drawable.setIsDisabled(info.isDisabled());
+        return drawable;
+    }
+
+    /**
+     * Creates a drawable for the provided BitmapInfo
+     */
+    public static FastBitmapDrawable newIcon(Context context, BitmapInfo info) {
+        if (info instanceof Factory) {
+            return ((Factory) info).newDrawable();
+        } else if (info.isLowRes()) {
+            return new PlaceHolderIconDrawable(info, context);
+        } else {
+            return new FastBitmapDrawable(info);
+        }
+    }
 }
diff --git a/src/com/android/launcher3/IconProvider.java b/src/com/android/launcher3/IconProvider.java
deleted file mode 100644
index 0f006f7..0000000
--- a/src/com/android/launcher3/IconProvider.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.android.launcher3;
-
-import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
-
-import android.content.pm.LauncherActivityInfo;
-import android.graphics.drawable.Drawable;
-
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.ResourceBasedOverride;
-
-public class IconProvider implements ResourceBasedOverride {
-
-    public static MainThreadInitializedObject<IconProvider> INSTANCE =
-            forOverride(IconProvider.class, R.string.icon_provider_class);
-
-    public IconProvider() { }
-
-    public String getSystemStateForPackage(String systemState, String packageName) {
-        return systemState;
-    }
-
-    /**
-     * @param flattenDrawable true if the caller does not care about the specification of the
-     *                        original icon as long as the flattened version looks the same.
-     */
-    public Drawable getIcon(LauncherActivityInfo info, int iconDpi, boolean flattenDrawable) {
-        return info.getIcon(iconDpi);
-    }
-}
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index c717d1a..79f4821 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -30,12 +30,14 @@
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.icons.IconProvider;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.notification.NotificationListener;
 import com.android.launcher3.pm.InstallSessionTracker;
 import com.android.launcher3.pm.PackageInstallerCompat;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.SecureSettingsObserver;
 import com.android.launcher3.util.SimpleBroadcastReceiver;
 import com.android.launcher3.widget.custom.CustomWidgetManager;
@@ -57,6 +59,7 @@
 
     private final InstallSessionTracker mInstallSessionTracker;
     private final SimpleBroadcastReceiver mModelChangeReceiver;
+    private final SafeCloseable mCalendarChangeTracker;
 
     public static LauncherAppState getInstance(final Context context) {
         return INSTANCE.get(context);
@@ -92,6 +95,10 @@
         if (FeatureFlags.IS_DOGFOOD_BUILD) {
             mModelChangeReceiver.register(mContext, ACTION_FORCE_ROLOAD);
         }
+
+        mCalendarChangeTracker = IconProvider.registerIconChangeListener(mContext,
+                mModel::onAppIconChanged, MODEL_EXECUTOR.getHandler());
+
         // TODO: remove listener on terminate
         FeatureFlags.APP_SEARCH_IMPROVEMENTS.addChangeListener(context, mModel::forceReload);
         CustomWidgetManager.INSTANCE.get(mContext)
@@ -143,6 +150,7 @@
         mContext.unregisterReceiver(mModelChangeReceiver);
         mContext.getSystemService(LauncherApps.class).unregisterCallback(mModel);
         mInstallSessionTracker.unregister();
+        mCalendarChangeTracker.close();
         CustomWidgetManager.INSTANCE.get(mContext).setWidgetRefreshCallback(null);
 
         if (mNotificationDotsObserver != null) {
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 93304a1..1e25c0c 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -21,6 +21,7 @@
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageInstaller;
@@ -31,6 +32,7 @@
 import android.util.Pair;
 
 import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
@@ -53,7 +55,6 @@
 import com.android.launcher3.pm.InstallSessionTracker;
 import com.android.launcher3.pm.PackageInstallInfo;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.IntSparseArrayMap;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.PackageUserKey;
@@ -210,9 +211,21 @@
         enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, true));
     }
 
-    public void updatePinnedShortcuts(String packageName, List<ShortcutInfo> shortcuts,
-            UserHandle user) {
-        enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, false));
+    /**
+     * Called when the icon for an app changes, outside of package event
+     */
+    @WorkerThread
+    public void onAppIconChanged(String packageName, UserHandle user) {
+        // Update the icon for the calendar package
+        Context context = mApp.getContext();
+        onPackageChanged(packageName, user);
+
+        List<ShortcutInfo> pinnedShortcuts = DeepShortcutManager.getInstance(context)
+                .queryForPinnedShortcuts(packageName, user);
+        if (!pinnedShortcuts.isEmpty()) {
+            enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, pinnedShortcuts, user,
+                    false));
+        }
     }
 
     public void onBroadcastIntent(Intent intent) {
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index aa6e61c..2032845 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -63,6 +63,7 @@
 import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
 import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.graphics.TintedDrawableSpan;
+import com.android.launcher3.icons.IconProvider;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.pm.ShortcutConfigActivityInfo;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
@@ -518,19 +519,20 @@
     }
 
     /**
-     * Returns the full drawable for {@param info}.
+     * Returns the full drawable for info without any flattening or pre-processing.
+     *
      * @param outObj this is set to the internal data associated with {@param info},
      *               eg {@link LauncherActivityInfo} or {@link ShortcutInfo}.
      */
     public static Drawable getFullDrawable(Launcher launcher, ItemInfo info, int width, int height,
-            boolean flattenDrawable, Object[] outObj) {
+            Object[] outObj) {
         LauncherAppState appState = LauncherAppState.getInstance(launcher);
         if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
             LauncherActivityInfo activityInfo = launcher.getSystemService(LauncherApps.class)
                     .resolveActivity(info.getIntent(), info.user);
             outObj[0] = activityInfo;
-            return (activityInfo != null) ? appState.getIconCache()
-                    .getFullResIcon(activityInfo, flattenDrawable) : null;
+            return activityInfo == null ? null : new IconProvider(launcher).getIconForUI(
+                    activityInfo, launcher.getDeviceProfile().inv.fillResIconDpi);
         } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
             if (info instanceof PendingAddShortcutInfo) {
                 ShortcutConfigActivityInfo activityInfo =
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index c5e74ef..37b58d3 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -23,16 +23,19 @@
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
 import android.os.CancellationSignal;
 import android.os.Process;
 import android.os.UserHandle;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.Pair;
 
 import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
 
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.UserManagerCompat;
@@ -78,6 +81,9 @@
     private final UserManagerCompat mUserManager;
     private final CacheDb mDb;
 
+    private final UserHandle mMyUser = Process.myUserHandle();
+    private final ArrayMap<UserHandle, Bitmap> mUserBadges = new ArrayMap<>();
+
     public WidgetPreviewLoader(Context context, IconCache iconCache) {
         mContext = context;
         mIconCache = iconCache;
@@ -86,6 +92,51 @@
     }
 
     /**
+     * Returns a drawable that can be used as a badge for the user or null.
+     */
+    @UiThread
+    public Drawable getBadgeForUser(UserHandle user, int badgeSize) {
+        if (mMyUser.equals(user)) {
+            return null;
+        }
+
+        Bitmap badgeBitmap = getUserBadge(user, badgeSize);
+        FastBitmapDrawable d = new FastBitmapDrawable(badgeBitmap);
+        d.setFilterBitmap(true);
+        d.setBounds(0, 0, badgeBitmap.getWidth(), badgeBitmap.getHeight());
+        return d;
+    }
+
+    private Bitmap getUserBadge(UserHandle user, int badgeSize) {
+        synchronized (mUserBadges) {
+            Bitmap badgeBitmap = mUserBadges.get(user);
+            if (badgeBitmap != null) {
+                return badgeBitmap;
+            }
+
+            final Resources res = mContext.getResources();
+            badgeBitmap = Bitmap.createBitmap(badgeSize, badgeSize, Bitmap.Config.ARGB_8888);
+
+            Drawable drawable = mContext.getPackageManager().getUserBadgedDrawableForDensity(
+                    new BitmapDrawable(res, badgeBitmap), user,
+                    new Rect(0, 0, badgeSize, badgeSize),
+                    0);
+            if (drawable instanceof BitmapDrawable) {
+                badgeBitmap = ((BitmapDrawable) drawable).getBitmap();
+            } else {
+                badgeBitmap.eraseColor(Color.TRANSPARENT);
+                Canvas c = new Canvas(badgeBitmap);
+                drawable.setBounds(0, 0, badgeSize, badgeSize);
+                drawable.draw(c);
+                c.setBitmap(null);
+            }
+
+            mUserBadges.put(user, badgeBitmap);
+            return badgeBitmap;
+        }
+    }
+
+    /**
      * Generates the widget preview on {@link AsyncTask#THREAD_POOL_EXECUTOR}. Must be
      * called on UI thread
      *
@@ -106,8 +157,8 @@
 
     public void refresh() {
         mDb.clear();
-
     }
+
     /**
      * The DB holds the generated previews for various components. Previews can also have different
      * sizes (landscape vs portrait).
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index f66d07e..145885a 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -216,8 +216,7 @@
                 Object[] outObj = new Object[1];
                 int w = mBitmap.getWidth();
                 int h = mBitmap.getHeight();
-                Drawable dr = Utilities.getFullDrawable(mLauncher, info, w, h,
-                        false /* flattenDrawable */, outObj);
+                Drawable dr = Utilities.getFullDrawable(mLauncher, info, w, h, outObj);
 
                 if (dr instanceof AdaptiveIconDrawable) {
                     int blurMargin = (int) mLauncher.getResources()
diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java
index 2d817e6..5b3a05e 100644
--- a/src/com/android/launcher3/folder/PreviewItemManager.java
+++ b/src/com/android/launcher3/folder/PreviewItemManager.java
@@ -16,10 +16,12 @@
 
 package com.android.launcher3.folder;
 
+import static com.android.launcher3.FastBitmapDrawable.newIcon;
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ENTER_INDEX;
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.EXIT_INDEX;
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
 import static com.android.launcher3.folder.FolderIcon.DROP_IN_ANIMATION_DURATION;
+import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -38,7 +40,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.graphics.DrawableFactory;
 import com.android.launcher3.graphics.PreloadIconDrawable;
 
 import java.util.ArrayList;
@@ -66,7 +67,6 @@
 
     private final Context mContext;
     private final FolderIcon mIcon;
-    private final DrawableFactory mDrawableFactory;
     private final int mIconSize;
 
     // These variables are all associated with the drawing of the preview; they are stored
@@ -94,7 +94,6 @@
     public PreviewItemManager(FolderIcon icon) {
         mContext = icon.getContext();
         mIcon = icon;
-        mDrawableFactory = DrawableFactory.INSTANCE.get(mContext);
         mIconSize = Launcher.getLauncher(mContext).getDeviceProfile().folderChildIconSizePx;
     }
 
@@ -395,11 +394,11 @@
 
     private void setDrawable(PreviewItemDrawingParams p, WorkspaceItemInfo item) {
         if (item.hasPromiseIconUi()) {
-            PreloadIconDrawable drawable = mDrawableFactory.newPendingIcon(mContext, item);
+            PreloadIconDrawable drawable = newPendingIcon(mContext, item);
             drawable.setLevel(item.getInstallProgress());
             p.drawable = drawable;
         } else {
-            p.drawable = mDrawableFactory.newIcon(mContext, item);
+            p.drawable = newIcon(mContext, item);
         }
         p.drawable.setBounds(0, 0, mIconSize, mIconSize);
         p.item = item;
diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java
deleted file mode 100644
index 13dbab5..0000000
--- a/src/com/android/launcher3/graphics/DrawableFactory.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.graphics;
-
-import static com.android.launcher3.graphics.IconShape.getShapePath;
-import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
-
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.Process;
-import android.os.UserHandle;
-import android.util.ArrayMap;
-
-import androidx.annotation.UiThread;
-
-import com.android.launcher3.FastBitmapDrawable;
-import com.android.launcher3.ItemInfoWithIcon;
-import com.android.launcher3.R;
-import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.ResourceBasedOverride;
-
-/**
- * Factory for creating new drawables.
- */
-public class DrawableFactory implements ResourceBasedOverride {
-
-    public static final MainThreadInitializedObject<DrawableFactory> INSTANCE =
-            forOverride(DrawableFactory.class, R.string.drawable_factory_class);
-
-    protected final UserHandle mMyUser = Process.myUserHandle();
-    protected final ArrayMap<UserHandle, Bitmap> mUserBadges = new ArrayMap<>();
-
-    /**
-     * Returns a FastBitmapDrawable with the icon.
-     */
-    public FastBitmapDrawable newIcon(Context context, ItemInfoWithIcon info) {
-        FastBitmapDrawable drawable = info.usingLowResIcon()
-                ? new PlaceHolderIconDrawable(info.bitmap, getShapePath(), context)
-                : new FastBitmapDrawable(info.bitmap);
-        drawable.setIsDisabled(info.isDisabled());
-        return drawable;
-    }
-
-    public FastBitmapDrawable newIcon(Context context, BitmapInfo info, ActivityInfo target) {
-        return info.isLowRes()
-                ? new PlaceHolderIconDrawable(info, getShapePath(), context)
-                : new FastBitmapDrawable(info);
-    }
-
-    /**
-     * Returns a FastBitmapDrawable with the icon.
-     */
-    public PreloadIconDrawable newPendingIcon(Context context, ItemInfoWithIcon info) {
-        return new PreloadIconDrawable(info, getShapePath(), context);
-    }
-
-    /**
-     * Returns a drawable that can be used as a badge for the user or null.
-     */
-    @UiThread
-    public Drawable getBadgeForUser(UserHandle user, Context context, int badgeSize) {
-        if (mMyUser.equals(user)) {
-            return null;
-        }
-
-        Bitmap badgeBitmap = getUserBadge(user, context, badgeSize);
-        FastBitmapDrawable d = new FastBitmapDrawable(badgeBitmap);
-        d.setFilterBitmap(true);
-        d.setBounds(0, 0, badgeBitmap.getWidth(), badgeBitmap.getHeight());
-        return d;
-    }
-
-    protected synchronized Bitmap getUserBadge(UserHandle user, Context context, int badgeSize) {
-        Bitmap badgeBitmap = mUserBadges.get(user);
-        if (badgeBitmap != null) {
-            return badgeBitmap;
-        }
-
-        final Resources res = context.getApplicationContext().getResources();
-        badgeBitmap = Bitmap.createBitmap(badgeSize, badgeSize, Bitmap.Config.ARGB_8888);
-
-        Drawable drawable = context.getPackageManager().getUserBadgedDrawableForDensity(
-                new BitmapDrawable(res, badgeBitmap), user, new Rect(0, 0, badgeSize, badgeSize),
-                0);
-        if (drawable instanceof BitmapDrawable) {
-            badgeBitmap = ((BitmapDrawable) drawable).getBitmap();
-        } else {
-            badgeBitmap.eraseColor(Color.TRANSPARENT);
-            Canvas c = new Canvas(badgeBitmap);
-            drawable.setBounds(0, 0, badgeSize, badgeSize);
-            drawable.draw(c);
-            c.setBitmap(null);
-        }
-
-        mUserBadges.put(user, badgeBitmap);
-        return badgeBitmap;
-    }
-}
diff --git a/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java b/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java
index c6f807f..d347e8f 100644
--- a/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java
@@ -17,8 +17,9 @@
 
 import static androidx.core.graphics.ColorUtils.compositeColors;
 
+import static com.android.launcher3.graphics.IconShape.getShapePath;
+
 import android.content.Context;
-import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Path;
 import android.graphics.Rect;
@@ -36,16 +37,12 @@
     // 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(BitmapInfo info, Context context) {
+        super(info);
 
-    protected PlaceHolderIconDrawable(Bitmap b, int iconColor, Path progressPath, Context context) {
-        super(b, iconColor);
-
-        mProgressPath = progressPath;
+        mProgressPath = getShapePath();
         mPaint.setColor(compositeColors(
-                Themes.getAttrColor(context, R.attr.loadingIconColor), iconColor));
+                Themes.getAttrColor(context, R.attr.loadingIconColor), info.color));
     }
 
     @Override
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
index acdf942..b0e1db1 100644
--- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -18,6 +18,7 @@
 package com.android.launcher3.graphics;
 
 import static com.android.launcher3.graphics.IconShape.DEFAULT_PATH_SIZE;
+import static com.android.launcher3.graphics.IconShape.getShapePath;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -101,13 +102,10 @@
 
     private ObjectAnimator mCurrentAnim;
 
-    /**
-     * @param progressPath fixed path in the bounds [0, 0, 100, 100] representing a progress bar.
-     */
-    public PreloadIconDrawable(ItemInfoWithIcon info, Path progressPath, Context context) {
+    public PreloadIconDrawable(ItemInfoWithIcon info, Context context) {
         super(info.bitmap);
         mItem = info;
-        mProgressPath = progressPath;
+        mProgressPath = getShapePath();
         mScaledTrackPath = new Path();
         mScaledProgressPath = new Path();
 
@@ -289,4 +287,11 @@
         }
         invalidateSelf();
     }
+
+    /**
+     * Returns a FastBitmapDrawable with the icon.
+     */
+    public static PreloadIconDrawable newPendingIcon(Context context, ItemInfoWithIcon info) {
+        return new PreloadIconDrawable(info, context);
+    }
 }
diff --git a/src/com/android/launcher3/icons/ClockDrawableWrapper.java b/src/com/android/launcher3/icons/ClockDrawableWrapper.java
new file mode 100644
index 0000000..b7dd092
--- /dev/null
+++ b/src/com/android/launcher3/icons/ClockDrawableWrapper.java
@@ -0,0 +1,328 @@
+/*
+ * 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.annotation.TargetApi;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.launcher3.FastBitmapDrawable;
+
+import java.util.Calendar;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Wrapper over {@link AdaptiveIconDrawable} to intercept icon flattening logic for dynamic
+ * clock icons
+ */
+@TargetApi(Build.VERSION_CODES.O)
+public class ClockDrawableWrapper extends AdaptiveIconDrawable implements BitmapInfo.Extender {
+
+    private static final String TAG = "ClockDrawableWrapper";
+
+    private static final boolean DISABLE_SECONDS = true;
+
+    // Time after which the clock icon should check for an update. The actual invalidate
+    // will only happen in case of any change.
+    public static final long TICK_MS = DISABLE_SECONDS ? TimeUnit.MINUTES.toMillis(1) : 200L;
+
+    private static final String LAUNCHER_PACKAGE = "com.android.launcher3";
+    private static final String ROUND_ICON_METADATA_KEY = LAUNCHER_PACKAGE
+            + ".LEVEL_PER_TICK_ICON_ROUND";
+    private static final String HOUR_INDEX_METADATA_KEY = LAUNCHER_PACKAGE + ".HOUR_LAYER_INDEX";
+    private static final String MINUTE_INDEX_METADATA_KEY = LAUNCHER_PACKAGE
+            + ".MINUTE_LAYER_INDEX";
+    private static final String SECOND_INDEX_METADATA_KEY = LAUNCHER_PACKAGE
+            + ".SECOND_LAYER_INDEX";
+    private static final String DEFAULT_HOUR_METADATA_KEY = LAUNCHER_PACKAGE
+            + ".DEFAULT_HOUR";
+    private static final String DEFAULT_MINUTE_METADATA_KEY = LAUNCHER_PACKAGE
+            + ".DEFAULT_MINUTE";
+    private static final String DEFAULT_SECOND_METADATA_KEY = LAUNCHER_PACKAGE
+            + ".DEFAULT_SECOND";
+
+    /* Number of levels to jump per second for the second hand */
+    private static final int LEVELS_PER_SECOND = 10;
+
+    public static final int INVALID_VALUE = -1;
+
+    private final AnimationInfo mAnimationInfo = new AnimationInfo();
+    private int mTargetSdkVersion;
+
+    public ClockDrawableWrapper(AdaptiveIconDrawable base) {
+        super(base.getBackground(), base.getForeground());
+    }
+
+    /**
+     * Loads and returns the wrapper from the provided package, or returns null
+     * if it is unable to load.
+     */
+    public static ClockDrawableWrapper forPackage(Context context, String pkg, int iconDpi) {
+        try {
+            PackageManager pm = context.getPackageManager();
+            ApplicationInfo appInfo =  pm.getApplicationInfo(pkg,
+                    PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA);
+            final Bundle metadata = appInfo.metaData;
+            if (metadata == null) {
+                return null;
+            }
+            int drawableId = metadata.getInt(ROUND_ICON_METADATA_KEY, 0);
+            if (drawableId == 0) {
+                return null;
+            }
+
+            Drawable drawable = pm.getResourcesForApplication(appInfo).getDrawableForDensity(
+                    drawableId, iconDpi).mutate();
+            if (!(drawable instanceof AdaptiveIconDrawable)) {
+                return null;
+            }
+
+            ClockDrawableWrapper wrapper =
+                    new ClockDrawableWrapper((AdaptiveIconDrawable) drawable);
+            wrapper.mTargetSdkVersion = appInfo.targetSdkVersion;
+            AnimationInfo info = wrapper.mAnimationInfo;
+
+            info.baseDrawableState = drawable.getConstantState();
+
+            info.hourLayerIndex = metadata.getInt(HOUR_INDEX_METADATA_KEY, INVALID_VALUE);
+            info.minuteLayerIndex = metadata.getInt(MINUTE_INDEX_METADATA_KEY, INVALID_VALUE);
+            info.secondLayerIndex = metadata.getInt(SECOND_INDEX_METADATA_KEY, INVALID_VALUE);
+
+            info.defaultHour = metadata.getInt(DEFAULT_HOUR_METADATA_KEY, 0);
+            info.defaultMinute = metadata.getInt(DEFAULT_MINUTE_METADATA_KEY, 0);
+            info.defaultSecond = metadata.getInt(DEFAULT_SECOND_METADATA_KEY, 0);
+
+            LayerDrawable foreground = (LayerDrawable) wrapper.getForeground();
+            int layerCount = foreground.getNumberOfLayers();
+            if (info.hourLayerIndex < 0 || info.hourLayerIndex >= layerCount) {
+                info.hourLayerIndex = INVALID_VALUE;
+            }
+            if (info.minuteLayerIndex < 0 || info.minuteLayerIndex >= layerCount) {
+                info.minuteLayerIndex = INVALID_VALUE;
+            }
+            if (info.secondLayerIndex < 0 || info.secondLayerIndex >= layerCount) {
+                info.secondLayerIndex = INVALID_VALUE;
+            } else if (DISABLE_SECONDS) {
+                foreground.setDrawable(info.secondLayerIndex, null);
+                info.secondLayerIndex = INVALID_VALUE;
+            }
+            return wrapper;
+        } catch (Exception e) {
+            Log.d(TAG, "Unable to load clock drawable info", e);
+        }
+        return null;
+    }
+
+    @Override
+    public BitmapInfo getExtendedInfo(Bitmap bitmap, int color, BaseIconFactory iconFactory) {
+        iconFactory.disableColorExtraction();
+        float [] scale = new float[1];
+        AdaptiveIconDrawable background = new AdaptiveIconDrawable(
+                getBackground().getConstantState().newDrawable(), null);
+        BitmapInfo bitmapInfo = iconFactory.createBadgedIconBitmap(background,
+                Process.myUserHandle(), mTargetSdkVersion, false, scale);
+
+        return new ClockBitmapInfo(bitmap, color, scale[0], mAnimationInfo, bitmapInfo.icon);
+    }
+
+    @Override
+    public void prepareToDrawOnUi() {
+        mAnimationInfo.applyTime(Calendar.getInstance(), (LayerDrawable) getForeground());
+    }
+
+    private static class AnimationInfo {
+
+        public ConstantState baseDrawableState;
+
+        public int hourLayerIndex;
+        public int minuteLayerIndex;
+        public int secondLayerIndex;
+        public int defaultHour;
+        public int defaultMinute;
+        public int defaultSecond;
+
+        boolean applyTime(Calendar time, LayerDrawable foregroundDrawable) {
+            time.setTimeInMillis(System.currentTimeMillis());
+
+            // We need to rotate by the difference from the default time if one is specified.
+            int convertedHour = (time.get(Calendar.HOUR) + (12 - defaultHour)) % 12;
+            int convertedMinute = (time.get(Calendar.MINUTE) + (60 - defaultMinute)) % 60;
+            int convertedSecond = (time.get(Calendar.SECOND) + (60 - defaultSecond)) % 60;
+
+            boolean invalidate = false;
+            if (hourLayerIndex != INVALID_VALUE) {
+                final Drawable hour = foregroundDrawable.getDrawable(hourLayerIndex);
+                if (hour.setLevel(convertedHour * 60 + time.get(Calendar.MINUTE))) {
+                    invalidate = true;
+                }
+            }
+
+            if (minuteLayerIndex != INVALID_VALUE) {
+                final Drawable minute = foregroundDrawable.getDrawable(minuteLayerIndex);
+                if (minute.setLevel(time.get(Calendar.HOUR) * 60 + convertedMinute)) {
+                    invalidate = true;
+                }
+            }
+
+            if (secondLayerIndex != INVALID_VALUE) {
+                final Drawable second = foregroundDrawable.getDrawable(secondLayerIndex);
+                if (second.setLevel(convertedSecond * LEVELS_PER_SECOND)) {
+                    invalidate = true;
+                }
+            }
+
+            return invalidate;
+        }
+    }
+
+    private static class ClockBitmapInfo extends BitmapInfo implements FastBitmapDrawable.Factory {
+
+        public final float scale;
+        public final int offset;
+        public final AnimationInfo animInfo;
+        public final Bitmap mFlattenedBackground;
+
+        ClockBitmapInfo(Bitmap icon, int color, float scale, AnimationInfo animInfo,
+                Bitmap background) {
+            super(icon, color);
+            this.scale = scale;
+            this.animInfo = animInfo;
+            this.offset = (int) Math.ceil(ShadowGenerator.BLUR_FACTOR * icon.getWidth());
+            this.mFlattenedBackground = background;
+        }
+
+        @Override
+        public FastBitmapDrawable newDrawable() {
+            return new ClockIconDrawable(this);
+        }
+    }
+
+    private static class ClockIconDrawable extends FastBitmapDrawable implements Runnable {
+
+        private final Calendar mTime = Calendar.getInstance();
+
+        private final ClockBitmapInfo mInfo;
+
+        private final AdaptiveIconDrawable mFullDrawable;
+        private final LayerDrawable mForeground;
+
+        ClockIconDrawable(ClockBitmapInfo clockInfo) {
+            super(clockInfo);
+
+            mInfo = clockInfo;
+
+            mFullDrawable = (AdaptiveIconDrawable) mInfo.animInfo.baseDrawableState.newDrawable();
+            mForeground = (LayerDrawable) mFullDrawable.getForeground();
+        }
+
+        @Override
+        protected void onBoundsChange(Rect bounds) {
+            super.onBoundsChange(bounds);
+            mFullDrawable.setBounds(bounds);
+        }
+
+        @Override
+        public void drawInternal(Canvas canvas, Rect bounds) {
+            if (mInfo == null) {
+                super.drawInternal(canvas, bounds);
+                return;
+            }
+            // draw the background that is already flattened to a bitmap
+            canvas.drawBitmap(mInfo.mFlattenedBackground, null, bounds, mPaint);
+
+            // prepare and draw the foreground
+            mInfo.animInfo.applyTime(mTime, mForeground);
+
+            canvas.scale(mInfo.scale, mInfo.scale,
+                    bounds.exactCenterX() + mInfo.offset, bounds.exactCenterY() + mInfo.offset);
+            canvas.clipPath(mFullDrawable.getIconMask());
+            mForeground.draw(canvas);
+
+            reschedule();
+        }
+
+        @Override
+        protected void updateFilter() {
+            super.updateFilter();
+            mFullDrawable.setColorFilter(mPaint.getColorFilter());
+        }
+
+        @Override
+        public void run() {
+            if (mInfo.animInfo.applyTime(mTime, mForeground)) {
+                invalidateSelf();
+            } else {
+                reschedule();
+            }
+        }
+
+        @Override
+        public boolean setVisible(boolean visible, boolean restart) {
+            boolean result = super.setVisible(visible, restart);
+            if (visible) {
+                reschedule();
+            } else {
+                unscheduleSelf(this);
+            }
+            return result;
+        }
+
+        private void reschedule() {
+            if (!isVisible()) {
+                return;
+            }
+
+            unscheduleSelf(this);
+            final long upTime = SystemClock.uptimeMillis();
+            final long step = TICK_MS; /* tick every 200 ms */
+            scheduleSelf(this, upTime - ((upTime % step)) + step);
+        }
+
+        @Override
+        public ConstantState getConstantState() {
+            return new ClockConstantState(mInfo, isDisabled());
+        }
+
+        private static class ClockConstantState extends MyConstantState {
+
+            private final ClockBitmapInfo mInfo;
+
+            ClockConstantState(ClockBitmapInfo info, boolean isDisabled) {
+                super(info.icon, info.color, isDisabled);
+                mInfo = info;
+            }
+
+            @Override
+            public FastBitmapDrawable newDrawable() {
+                ClockIconDrawable drawable = new ClockIconDrawable(mInfo);
+                drawable.setIsDisabled(mIsDisabled);
+                return drawable;
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 13dc08f..f7faca6 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -37,7 +37,6 @@
 import androidx.annotation.NonNull;
 
 import com.android.launcher3.AppInfo;
-import com.android.launcher3.IconProvider;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.LauncherFiles;
@@ -50,6 +49,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.util.ComponentKey;
 import com.android.launcher3.util.InstantAppResolver;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Preconditions;
@@ -81,7 +81,7 @@
         mLauncherApps = mContext.getSystemService(LauncherApps.class);
         mUserManager = UserManagerCompat.getInstance(mContext);
         mInstantAppResolver = InstantAppResolver.newInstance(mContext);
-        mIconProvider = IconProvider.INSTANCE.get(context);
+        mIconProvider = new IconProvider(context);
     }
 
     @Override
@@ -230,11 +230,7 @@
     }
 
     public Drawable getFullResIcon(LauncherActivityInfo info) {
-        return getFullResIcon(info, true);
-    }
-
-    public Drawable getFullResIcon(LauncherActivityInfo info, boolean flattenDrawable) {
-        return mIconProvider.getIcon(info, mIconDpi, flattenDrawable);
+        return mIconProvider.getIcon(info, mIconDpi);
     }
 
     public void updateSessionCache(PackageUserKey key, PackageInstaller.SessionInfo info) {
@@ -247,6 +243,15 @@
                 + ",flags_asi:" + FeatureFlags.APP_SEARCH_IMPROVEMENTS.get();
     }
 
+    @Override
+    protected boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) {
+        if (mIconProvider.isClockIcon(cacheKey)) {
+            // For clock icon, we always load the dynamic icon
+            return false;
+        }
+        return super.getEntryFromDB(cacheKey, entry, lowRes);
+    }
+
     public static abstract class IconLoadRequest extends HandlerRunnable {
         IconLoadRequest(Handler handler, Runnable endRunnable) {
             super(handler, endRunnable);
diff --git a/src/com/android/launcher3/icons/IconProvider.java b/src/com/android/launcher3/icons/IconProvider.java
new file mode 100644
index 0000000..26b7eae
--- /dev/null
+++ b/src/com/android/launcher3/icons/IconProvider.java
@@ -0,0 +1,251 @@
+/*
+ * 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.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Process;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.launcher3.R;
+import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.icons.BitmapInfo.Extender;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.SafeCloseable;
+
+import java.util.Calendar;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+
+/**
+ * Class to handle icon loading from different packages
+ */
+public class IconProvider {
+
+    private static final String TAG = "IconProvider";
+    private static final boolean DEBUG = false;
+
+    private static final String ICON_METADATA_KEY_PREFIX = ".dynamic_icons";
+
+    private static final String SYSTEM_STATE_SEPARATOR = " ";
+
+    // Default value returned if there are problems getting resources.
+    private static final int NO_ID = 0;
+
+    private static final BiFunction<LauncherActivityInfo, Integer, Drawable> LAI_LOADER =
+            LauncherActivityInfo::getIcon;
+
+    private static final BiFunction<ActivityInfo, PackageManager, Drawable> AI_LOADER =
+            ActivityInfo::loadUnbadgedIcon;
+
+
+    private final Context mContext;
+    private final ComponentName mCalendar;
+    private final ComponentName mClock;
+
+    public IconProvider(Context context) {
+        mContext = context;
+        mCalendar = parseComponentOrNull(context, R.string.calendar_component_name);
+        mClock = parseComponentOrNull(context, R.string.clock_component_name);
+    }
+
+    /**
+     * Adds any modification to the provided systemState for dynamic icons. This system state
+     * is used by caches to check for icon invalidation.
+     */
+    public String getSystemStateForPackage(String systemState, String packageName) {
+        if (mCalendar != null && mCalendar.getPackageName().equals(packageName)) {
+            return systemState + SYSTEM_STATE_SEPARATOR + getDay();
+        } else {
+            return systemState;
+        }
+    }
+
+    /**
+     * Loads the icon for the provided LauncherActivityInfo such that it can be drawn directly
+     * on the UI
+     */
+    public Drawable getIconForUI(LauncherActivityInfo info, int iconDpi) {
+        Drawable icon = getIcon(info, iconDpi);
+        if (icon instanceof BitmapInfo.Extender) {
+            ((Extender) icon).prepareToDrawOnUi();
+        }
+        return icon;
+    }
+
+    /**
+     * Loads the icon for the provided LauncherActivityInfo
+     */
+    public Drawable getIcon(LauncherActivityInfo info, int iconDpi) {
+        return getIcon(info.getApplicationInfo().packageName, info.getUser(),
+                info, iconDpi, LAI_LOADER);
+    }
+
+    /**
+     * Loads the icon for the provided activity info
+     */
+    public Drawable getIcon(ActivityInfo info, UserHandle user) {
+        return getIcon(info.applicationInfo.packageName, user, info, mContext.getPackageManager(),
+                AI_LOADER);
+    }
+
+    private <T, P> Drawable getIcon(String packageName, UserHandle user, T obj, P param,
+            BiFunction<T, P, Drawable> loader) {
+        Drawable icon = null;
+        if (mCalendar != null && mCalendar.getPackageName().equals(packageName)) {
+            icon = loadCalendarDrawable(0);
+        } else if (mClock != null
+                && mClock.getPackageName().equals(packageName)
+                && Process.myUserHandle().equals(user)) {
+            icon = loadClockDrawable(0);
+        }
+        return icon == null ? loader.apply(obj, param) : icon;
+    }
+
+    private Drawable loadCalendarDrawable(int iconDpi) {
+        PackageManager pm = mContext.getPackageManager();
+        try {
+            final Bundle metadata = pm.getActivityInfo(
+                    mCalendar,
+                    PackageManager.GET_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA)
+                    .metaData;
+            final Resources resources = pm.getResourcesForApplication(mCalendar.getPackageName());
+            final int id = getDynamicIconId(metadata, resources);
+            if (id != NO_ID) {
+                if (DEBUG) Log.d(TAG, "Got icon #" + id);
+                return resources.getDrawableForDensity(id, iconDpi, null /* theme */);
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            if (DEBUG) {
+                Log.d(TAG, "Could not get activityinfo or resources for package: "
+                        + mCalendar.getPackageName());
+            }
+        }
+        return null;
+    }
+
+    private Drawable loadClockDrawable(int iconDpi) {
+        return ClockDrawableWrapper.forPackage(mContext, mClock.getPackageName(), iconDpi);
+    }
+
+    protected boolean isClockIcon(ComponentKey key) {
+        return mClock != null && mClock.equals(key.componentName)
+                && Process.myUserHandle().equals(key.user);
+    }
+
+    /**
+     * @param metadata metadata of the default activity of Calendar
+     * @param resources from the Calendar package
+     * @return the resource id for today's Calendar icon; 0 if resources cannot be found.
+     */
+    private int getDynamicIconId(Bundle metadata, Resources resources) {
+        if (metadata == null) {
+            return NO_ID;
+        }
+        String key = mCalendar.getPackageName() + ICON_METADATA_KEY_PREFIX;
+        final int arrayId = metadata.getInt(key, NO_ID);
+        if (arrayId == NO_ID) {
+            return NO_ID;
+        }
+        try {
+            return resources.obtainTypedArray(arrayId).getResourceId(getDay(), NO_ID);
+        } catch (Resources.NotFoundException e) {
+            if (DEBUG) {
+                Log.d(TAG, "package defines '" + key + "' but corresponding array not found");
+            }
+            return NO_ID;
+        }
+    }
+
+    /**
+     * @return Today's day of the month, zero-indexed.
+     */
+    private int getDay() {
+        return Calendar.getInstance().get(Calendar.DAY_OF_MONTH) - 1;
+    }
+
+
+    /**
+     * Registers a callback to listen for calendar icon changes.
+     * The callback receives the packageName for the calendar icon
+     */
+    public static SafeCloseable registerIconChangeListener(Context context,
+            BiConsumer<String, UserHandle> callback, Handler handler) {
+        ComponentName calendar = parseComponentOrNull(context, R.string.calendar_component_name);
+        ComponentName clock = parseComponentOrNull(context, R.string.clock_component_name);
+
+        if (calendar == null && clock == null) {
+            return () -> { };
+        }
+
+        BroadcastReceiver receiver = new DateTimeChangeReceiver(callback);
+        final IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
+        if (calendar != null) {
+            filter.addAction(Intent.ACTION_TIME_CHANGED);
+            filter.addAction(Intent.ACTION_DATE_CHANGED);
+        }
+        context.registerReceiver(receiver, filter, null, handler);
+
+        return () -> context.unregisterReceiver(receiver);
+    }
+
+    private static class DateTimeChangeReceiver extends BroadcastReceiver {
+
+        private final BiConsumer<String, UserHandle> mCallback;
+
+        DateTimeChangeReceiver(BiConsumer<String, UserHandle> callback) {
+            mCallback = callback;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
+                ComponentName clock = parseComponentOrNull(context, R.string.clock_component_name);
+                if (clock != null) {
+                    mCallback.accept(clock.getPackageName(), Process.myUserHandle());
+                }
+            }
+
+            ComponentName calendar =
+                    parseComponentOrNull(context, R.string.calendar_component_name);
+            if (calendar != null) {
+                for (UserHandle user : UserManagerCompat.getInstance(context).getUserProfiles()) {
+                    mCallback.accept(calendar.getPackageName(), user);
+                }
+            }
+
+        }
+    }
+
+    private static ComponentName parseComponentOrNull(Context context, int resId) {
+        String cn = context.getString(resId);
+        return TextUtils.isEmpty(cn) ? null : ComponentName.unflattenFromString(cn);
+
+    }
+}
diff --git a/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java b/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java
index a29d4fe..93de35a 100644
--- a/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java
+++ b/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java
@@ -20,7 +20,6 @@
 import android.content.pm.LauncherActivityInfo;
 import android.os.UserHandle;
 
-import com.android.launcher3.IconProvider;
 import com.android.launcher3.R;
 import com.android.launcher3.icons.cache.CachingLogic;
 import com.android.launcher3.util.ResourceBasedOverride;
@@ -57,9 +56,8 @@
     @Override
     public BitmapInfo loadIcon(Context context, LauncherActivityInfo object) {
         try (LauncherIcons li = LauncherIcons.obtain(context)) {
-            return li.createBadgedIconBitmap(
-                    IconProvider.INSTANCE.get(context)
-                            .getIcon(object, li.mFillResIconDpi, true /* flattenDrawable */),
+            return li.createBadgedIconBitmap(new IconProvider(context)
+                            .getIcon(object, li.mFillResIconDpi),
                     object.getUser(), object.getApplicationInfo().targetSdkVersion);
         }
     }
diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java
index 1c34dc4..3c4041a 100644
--- a/src/com/android/launcher3/icons/LauncherIcons.java
+++ b/src/com/android/launcher3/icons/LauncherIcons.java
@@ -31,7 +31,6 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.R;
 import com.android.launcher3.graphics.IconShape;
 import com.android.launcher3.model.PackageItemInfo;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
@@ -45,8 +44,6 @@
  */
 public class LauncherIcons extends BaseIconFactory implements AutoCloseable {
 
-    private static final String EXTRA_BADGEPKG = "badge_package";
-
     private static final Object sPoolSync = new Object();
     private static LauncherIcons sPool;
     private static int sPoolId = 0;
@@ -160,7 +157,7 @@
 
     public ItemInfoWithIcon getShortcutInfoBadge(ShortcutInfo shortcutInfo, IconCache cache) {
         ComponentName cn = shortcutInfo.getActivity();
-        String badgePkg = getBadgePackage(shortcutInfo);
+        String badgePkg = shortcutInfo.getPackage();
         boolean hasBadgePkgSet = !badgePkg.equals(shortcutInfo.getPackage());
         if (cn != null && !hasBadgePkgSet) {
             // Get the app info for the source activity.
@@ -178,14 +175,4 @@
             return pkgInfo;
         }
     }
-
-    private String getBadgePackage(ShortcutInfo si) {
-        String whitelistedPkg = mContext.getString(R.string.shortcutinfo_badgepkg_whitelist);
-        if (whitelistedPkg.equals(si.getPackage())
-                && si.getExtras() != null
-                && si.getExtras().containsKey(EXTRA_BADGEPKG)) {
-            return si.getExtras().getString(EXTRA_BADGEPKG);
-        }
-        return si.getPackage();
-    }
 }
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 45c0d90..c63d745 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -415,7 +415,7 @@
             int width = isFolderIcon ? originalView.getWidth() : (int) pos.width();
             int height = isFolderIcon ? originalView.getHeight() : (int) pos.height();
             if (supportsAdaptiveIcons) {
-                drawable = getFullDrawable(l, info, width, height, false, sTmpObjArray);
+                drawable = getFullDrawable(l, info, width, height, sTmpObjArray);
                 if (drawable instanceof AdaptiveIconDrawable) {
                     badge = getBadge(l, info, sTmpObjArray[0]);
                 } else {
@@ -428,7 +428,7 @@
                     // Similar to DragView, we simply use the BubbleTextView icon here.
                     drawable = btvIcon;
                 } else {
-                    drawable = getFullDrawable(l, info, width, height, false, sTmpObjArray);
+                    drawable = getFullDrawable(l, info, width, height, sTmpObjArray);
                 }
             }
         }
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index 3ece5f4..6038873 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -16,6 +16,9 @@
 
 package com.android.launcher3.widget;
 
+import static com.android.launcher3.FastBitmapDrawable.newIcon;
+import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
+
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -36,7 +39,6 @@
 import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.R;
-import com.android.launcher3.graphics.DrawableFactory;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
 import com.android.launcher3.model.PackageItemInfo;
@@ -133,19 +135,17 @@
             //   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.INSTANCE.get(getContext());
             if (mDisabledForSafeMode) {
-                FastBitmapDrawable disabledIcon = drawableFactory.newIcon(getContext(), info);
+                FastBitmapDrawable disabledIcon = newIcon(getContext(), info);
                 disabledIcon.setIsDisabled(true);
                 mCenterDrawable = disabledIcon;
                 mSettingIconDrawable = null;
             } else if (isReadyForClickSetup()) {
-                mCenterDrawable = drawableFactory.newIcon(getContext(), info);
+                mCenterDrawable = newIcon(getContext(), info);
                 mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate();
                 updateSettingColor(info.bitmap.color);
             } else {
-                mCenterDrawable = DrawableFactory.INSTANCE.get(getContext())
-                        .newPendingIcon(getContext(), info);
+                mCenterDrawable = 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 6944879..f713b33 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -36,7 +36,6 @@
 import com.android.launcher3.SimpleOnStylusPressListener;
 import com.android.launcher3.StylusEventHelper;
 import com.android.launcher3.WidgetPreviewLoader;
-import com.android.launcher3.graphics.DrawableFactory;
 import com.android.launcher3.icons.BaseIconFactory;
 import com.android.launcher3.model.WidgetItem;
 
@@ -182,10 +181,8 @@
             return;
         }
         if (bitmap != null) {
-            mWidgetImage.setBitmap(bitmap,
-                    DrawableFactory.INSTANCE.get(getContext()).getBadgeForUser(mItem.user,
-                            getContext(), BaseIconFactory.getBadgeSizeForIconSize(
-                                    mDeviceProfile.allAppsIconSizePx)));
+            mWidgetImage.setBitmap(bitmap, mWidgetPreviewLoader.getBadgeForUser(mItem.user,
+                    BaseIconFactory.getBadgeSizeForIconSize(mDeviceProfile.allAppsIconSizePx)));
             if (mAnimatePreview) {
                 mWidgetImage.setAlpha(0f);
                 ViewPropertyAnimator anim = mWidgetImage.animate();