Unifying various model update callbacks into one

Making the BubbleTextView.applyItem stateless so that it does not depend on the order of events

Bug: 390572144
Flag: EXEMPT refactor
Test: atest LauncherBindableItemsContainerTest
      atest BubbleTextViewTest
Change-Id: Ib9c0ac6c330d6f4e08c3db5772d35787fa056b65
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
index f905c5f..6815f97 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
@@ -25,7 +25,6 @@
 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
@@ -39,9 +38,9 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.function.Predicate;
 
 /**
@@ -114,15 +113,9 @@
         return modified;
     }
 
-
     @Override
-    public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) {
-        updateWorkspaceItems(updated, mContext);
-    }
-
-    @Override
-    public void bindRestoreItemsChange(HashSet<ItemInfo> updates) {
-        updateRestoreItems(updates, mContext);
+    public void bindItemsUpdated(Set<ItemInfo> updates) {
+        updateContainerItems(updates, mContext);
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index a85e5e0..9b7bb1c 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -214,7 +214,7 @@
         boolean animate = shouldAnimateIconChange(info);
         Drawable oldIcon = getIcon();
         int oldPlateColor = mPlateColor.currentColor;
-        applyFromWorkspaceItem(info, null);
+        applyFromWorkspaceItem(info);
 
         setContentDescription(
                 mIsPinned ? info.contentDescription :
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 315096c..d3684b2 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -29,6 +29,7 @@
 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INCREMENTAL_DOWNLOAD_ACTIVE;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -63,6 +64,7 @@
 import android.widget.TextView;
 
 import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 import androidx.annotation.VisibleForTesting;
@@ -366,11 +368,6 @@
         mDotScaleAnim.start();
     }
 
-    @UiThread
-    public void applyFromWorkspaceItem(WorkspaceItemInfo info) {
-        applyFromWorkspaceItem(info, null);
-    }
-
     @Override
     public void setAccessibilityDelegate(AccessibilityDelegate delegate) {
         if (delegate instanceof BaseAccessibilityDelegate) {
@@ -384,10 +381,10 @@
     }
 
     @UiThread
-    public void applyFromWorkspaceItem(WorkspaceItemInfo info, PreloadIconDrawable icon) {
+    public void applyFromWorkspaceItem(WorkspaceItemInfo info) {
         applyIconAndLabel(info);
         setItemInfo(info);
-        applyLoadingState(icon);
+
         applyDotState(info, false /* animate */);
         setDownloadStateContentDescription(info, info.getProgressLevel());
     }
@@ -395,17 +392,11 @@
     @UiThread
     public void applyFromApplicationInfo(AppInfo info) {
         applyIconAndLabel(info);
-
-        // We don't need to check the info since it's not a WorkspaceItemInfo
         setItemInfo(info);
 
-
         // Verify high res immediately
         verifyHighRes();
 
-        if ((info.runtimeStatusFlags & ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0) {
-            applyProgressLevel();
-        }
         applyDotState(info, false /* animate */);
         setDownloadStateContentDescription(info, info.getProgressLevel());
     }
@@ -449,6 +440,50 @@
     @VisibleForTesting
     @UiThread
     public void applyIconAndLabel(ItemInfoWithIcon info) {
+        FastBitmapDrawable oldIcon = mIcon;
+        if (!canReuseIcon(info)) {
+            setNonPendingIcon(info);
+        }
+        applyLabel(info);
+        maybeApplyProgressLevel(info, oldIcon);
+    }
+
+    /**
+     * Check if we can reuse icon so that any animation is preserved
+     */
+    private boolean canReuseIcon(ItemInfoWithIcon info) {
+        return mIcon instanceof PreloadIconDrawable p
+                && p.hasNotCompleted() && p.isSameInfo(info.bitmap);
+    }
+
+    /**
+     * Apply progress level to the icon if necessary
+     */
+    private void maybeApplyProgressLevel(ItemInfoWithIcon info, FastBitmapDrawable oldIcon) {
+        if (!shouldApplyProgressLevel(info, oldIcon)) {
+            return;
+        }
+        PreloadIconDrawable pendingIcon = applyProgressLevel(info);
+        boolean isNoLongerPending = info instanceof WorkspaceItemInfo wii
+                ? !wii.hasPromiseIconUi() : !info.isArchived();
+        if (isNoLongerPending && info.getProgressLevel() == 100 && pendingIcon != null) {
+            pendingIcon.maybePerformFinishedAnimation(
+                    (oldIcon instanceof PreloadIconDrawable p) ? p : pendingIcon,
+                    () -> setNonPendingIcon(
+                            (getTag() instanceof ItemInfoWithIcon iiwi) ? iiwi : info));
+        }
+    }
+
+    /**
+     * Check if progress level should be applied to the icon
+     */
+    private boolean shouldApplyProgressLevel(ItemInfoWithIcon info, FastBitmapDrawable oldIcon) {
+        return (info.runtimeStatusFlags & FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0
+                || (info instanceof WorkspaceItemInfo wii && wii.hasPromiseIconUi())
+                || (oldIcon instanceof PreloadIconDrawable p && p.hasNotCompleted());
+    }
+
+    private void setNonPendingIcon(ItemInfoWithIcon info) {
         ThemeManager themeManager = ThemeManager.INSTANCE.get(getContext());
         int flags = (shouldUseTheme()
                 && themeManager.isMonoThemeEnabled()) ? FLAG_THEMED : 0;
@@ -463,7 +498,6 @@
         mDotParams.appColor = iconDrawable.getIconColor();
         mDotParams.dotColor = Themes.getAttrColor(getContext(), R.attr.notificationDotColor);
         setIcon(iconDrawable);
-        applyLabel(info);
     }
 
     protected boolean shouldUseTheme() {
@@ -1070,38 +1104,10 @@
         mLongPressHelper.cancelLongPress();
     }
 
-    /**
-     * Applies the loading progress value to the progress bar.
-     *
-     * If this app is installing, the progress bar will be updated with the installation progress.
-     * If this app is installed and downloading incrementally, the progress bar will be updated
-     * with the total download progress.
-     */
-    public void applyLoadingState(PreloadIconDrawable icon) {
-        if (getTag() instanceof ItemInfoWithIcon) {
-            WorkspaceItemInfo info = (WorkspaceItemInfo) getTag();
-            if ((info.runtimeStatusFlags & FLAG_INCREMENTAL_DOWNLOAD_ACTIVE) != 0
-                    || info.hasPromiseIconUi()
-                    || (info.runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0
-                    || (icon != null)) {
-                updateProgressBarUi(info.getProgressLevel() == 100 ? icon : null);
-            }
-        }
-    }
-
-    private void updateProgressBarUi(PreloadIconDrawable oldIcon) {
-        FastBitmapDrawable originalIcon = mIcon;
-        PreloadIconDrawable preloadDrawable = applyProgressLevel();
-        if (preloadDrawable != null && oldIcon != null) {
-            preloadDrawable.maybePerformFinishedAnimation(oldIcon, () -> setIcon(originalIcon));
-        }
-    }
-
     /** Applies the given progress level to the this icon's progress bar. */
     @Nullable
-    public PreloadIconDrawable applyProgressLevel() {
-        if (!(getTag() instanceof ItemInfoWithIcon info)
-                || ((ItemInfoWithIcon) getTag()).isInactiveArchive()) {
+    private PreloadIconDrawable applyProgressLevel(ItemInfoWithIcon info) {
+        if (info.isInactiveArchive()) {
             return null;
         }
 
@@ -1115,23 +1121,16 @@
             setContentDescription(getContext()
                     .getString(R.string.app_waiting_download_title, info.title));
         }
-        if (mIcon != null) {
-            PreloadIconDrawable preloadIconDrawable;
-            if (mIcon instanceof PreloadIconDrawable) {
-                preloadIconDrawable = (PreloadIconDrawable) mIcon;
-                preloadIconDrawable.setLevel(progressLevel);
-                preloadIconDrawable.setIsDisabled(isIconDisabled(info));
-            } else {
-                preloadIconDrawable = makePreloadIcon();
-                setIcon(preloadIconDrawable);
-                if (info.isArchived() && Flags.useNewIconForArchivedApps()) {
-                    // reapply text without cloud icon as soon as unarchiving is triggered
-                    applyLabel(info);
-                }
-            }
-            return preloadIconDrawable;
+        PreloadIconDrawable pid;
+        if (mIcon instanceof PreloadIconDrawable p) {
+            pid = p;
+            pid.setLevel(progressLevel);
+            pid.setIsDisabled(isIconDisabled(info));
+        } else {
+            pid = makePreloadIcon(info);
+            setIcon(pid);
         }
-        return null;
+        return pid;
     }
 
     /**
@@ -1140,11 +1139,11 @@
      */
     @Nullable
     public PreloadIconDrawable makePreloadIcon() {
-        if (!(getTag() instanceof ItemInfoWithIcon)) {
-            return null;
-        }
+        return getTag() instanceof ItemInfoWithIcon info ? makePreloadIcon(info) : null;
+    }
 
-        ItemInfoWithIcon info = (ItemInfoWithIcon) getTag();
+    @NonNull
+    private PreloadIconDrawable makePreloadIcon(ItemInfoWithIcon info) {
         int progressLevel = info.getProgressLevel();
         final PreloadIconDrawable preloadDrawable = newPendingIcon(getContext(), info);
 
@@ -1163,7 +1162,7 @@
 
 
     public void applyDotState(ItemInfo itemInfo, boolean animate) {
-        if (mIcon instanceof FastBitmapDrawable) {
+        if (mIcon != null) {
             boolean wasDotted = mDotInfo != null;
             mDotInfo = mActivity.getDotInfoForItem(itemInfo);
             boolean isDotted = mDotInfo != null;
@@ -1212,7 +1211,7 @@
                 setContentDescription(getContext().getString(
                         R.string.app_archived_title, info.title));
             }
-        } else if ((info.runtimeStatusFlags & ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK)
+        } else if ((info.runtimeStatusFlags & FLAG_SHOW_DOWNLOAD_PROGRESS_MASK)
                 != 0) {
             String percentageString = NumberFormat.getPercentInstance()
                     .format(progressLevel * 0.01);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 7df4014..647d2ad 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -277,11 +277,11 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.Set;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
 import java.util.stream.Stream;
@@ -2598,25 +2598,12 @@
         mModelCallbacks.bindIncrementalDownloadProgressUpdated(app);
     }
 
-    @Override
-    public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets) {
-        mModelCallbacks.bindWidgetsRestored(widgets);
-    }
-
     /**
      * See {@code LauncherBindingDelegate}
      */
     @Override
-    public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) {
-        mModelCallbacks.bindWorkspaceItemsChanged(updated);
-    }
-
-    /**
-     * See {@code LauncherBindingDelegate}
-     */
-    @Override
-    public void bindRestoreItemsChange(HashSet<ItemInfo> updates) {
-        mModelCallbacks.bindRestoreItemsChange(updates);
+    public void bindItemsUpdated(Set<ItemInfo> updates) {
+        mModelCallbacks.bindItemsUpdated(updates);
     }
 
     /**
diff --git a/src/com/android/launcher3/ModelCallbacks.kt b/src/com/android/launcher3/ModelCallbacks.kt
index 5d32525..5338fb4 100644
--- a/src/com/android/launcher3/ModelCallbacks.kt
+++ b/src/com/android/launcher3/ModelCallbacks.kt
@@ -17,8 +17,6 @@
 import com.android.launcher3.model.StringCache
 import com.android.launcher3.model.data.AppInfo
 import com.android.launcher3.model.data.ItemInfo
-import com.android.launcher3.model.data.LauncherAppWidgetInfo
-import com.android.launcher3.model.data.WorkspaceItemInfo
 import com.android.launcher3.popup.PopupContainerWithArrow
 import com.android.launcher3.util.ComponentKey
 import com.android.launcher3.util.IntArray as LIntArray
@@ -215,29 +213,13 @@
         launcher.appsView.appsStore.updateProgressBar(app)
     }
 
-    override fun bindWidgetsRestored(widgets: ArrayList<LauncherAppWidgetInfo?>?) {
-        launcher.workspace.widgetsRestored(widgets)
-    }
-
-    /**
-     * Some shortcuts were updated in the background. Implementation of the method from
-     * LauncherModel.Callbacks.
-     *
-     * @param updated list of shortcuts which have changed.
-     */
-    override fun bindWorkspaceItemsChanged(updated: List<WorkspaceItemInfo?>) {
-        if (updated.isNotEmpty()) {
-            launcher.workspace.updateWorkspaceItems(updated, launcher)
-            PopupContainerWithArrow.dismissInvalidPopup(launcher)
-        }
-    }
-
     /**
      * Update the state of a package, typically related to install state. Implementation of the
      * method from LauncherModel.Callbacks.
      */
-    override fun bindRestoreItemsChange(updates: HashSet<ItemInfo?>?) {
-        launcher.workspace.updateRestoreItems(updates, launcher)
+    override fun bindItemsUpdated(updates: Set<ItemInfo>) {
+        launcher.workspace.updateContainerItems(updates, launcher)
+        PopupContainerWithArrow.dismissInvalidPopup(launcher)
     }
 
     /**
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 86c49d0..7d82179 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -53,8 +53,6 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.Message;
 import android.os.Parcelable;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -125,13 +123,9 @@
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.WallpaperOffsetInterpolator;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
-import com.android.launcher3.widget.LauncherWidgetHolder;
-import com.android.launcher3.widget.LauncherWidgetHolder.ProviderChangedListener;
 import com.android.launcher3.widget.NavigableAppWidgetHostView;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
-import com.android.launcher3.widget.PendingAppWidgetHostView;
-import com.android.launcher3.widget.WidgetManagerHelper;
 import com.android.launcher3.widget.util.WidgetSizes;
 import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlayCallbacks;
 import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlayTouchProxy;
@@ -664,9 +658,6 @@
             bindAndInitFirstWorkspaceScreen();
         }
 
-        // Remove any deferred refresh callbacks
-        mLauncher.mHandler.removeCallbacksAndMessages(DeferredWidgetRefresh.class);
-
         // Re-enable the layout transitions
         enableLayoutTransitions();
     }
@@ -3465,43 +3456,6 @@
         removeItemsByMatcher(matcher);
     }
 
-    public void widgetsRestored(final ArrayList<LauncherAppWidgetInfo> changedInfo) {
-        if (!changedInfo.isEmpty()) {
-            DeferredWidgetRefresh widgetRefresh = new DeferredWidgetRefresh(changedInfo,
-                    mLauncher.getAppWidgetHolder());
-
-            LauncherAppWidgetInfo item = changedInfo.get(0);
-            final AppWidgetProviderInfo widgetInfo;
-            WidgetManagerHelper widgetHelper = new WidgetManagerHelper(getContext());
-            if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
-                widgetInfo = widgetHelper.findProvider(item.providerName, item.user);
-            } else {
-                widgetInfo = widgetHelper.getLauncherAppWidgetInfo(item.appWidgetId,
-                        item.getTargetComponent());
-            }
-
-            if (widgetInfo != null) {
-                // Re-inflate the widgets which have changed status
-                widgetRefresh.run();
-            } else {
-                // widgetRefresh will automatically run when the packages are updated.
-                // For now just update the progress bars
-                mapOverItems(new ItemOperator() {
-                    @Override
-                    public boolean evaluate(ItemInfo info, View view) {
-                        if (view instanceof PendingAppWidgetHostView
-                                && changedInfo.contains(info)) {
-                            ((LauncherAppWidgetInfo) info).installProgress = 100;
-                            ((PendingAppWidgetHostView) view).applyState();
-                        }
-                        // process all the shortcuts
-                        return false;
-                    }
-                });
-            }
-        }
-    }
-
     public boolean isOverlayShown() {
         return mOverlayShown;
     }
@@ -3608,62 +3562,6 @@
         return mLauncher.getCellPosMapper();
     }
 
-    /**
-     * Used as a workaround to ensure that the AppWidgetService receives the
-     * PACKAGE_ADDED broadcast before updating widgets.
-     */
-    private class DeferredWidgetRefresh implements Runnable, ProviderChangedListener {
-        private final ArrayList<LauncherAppWidgetInfo> mInfos;
-        private final LauncherWidgetHolder mWidgetHolder;
-        private final Handler mHandler;
-
-        private boolean mRefreshPending;
-
-        DeferredWidgetRefresh(ArrayList<LauncherAppWidgetInfo> infos,
-                              LauncherWidgetHolder holder) {
-            mInfos = infos;
-            mWidgetHolder = holder;
-            mHandler = mLauncher.mHandler;
-            mRefreshPending = true;
-
-            mWidgetHolder.addProviderChangeListener(this);
-            // Force refresh after 10 seconds, if we don't get the provider changed event.
-            // This could happen when the provider is no longer available in the app.
-            Message msg = Message.obtain(mHandler, this);
-            msg.obj = DeferredWidgetRefresh.class;
-            mHandler.sendMessageDelayed(msg, 10000);
-        }
-
-        @Override
-        public void run() {
-            mWidgetHolder.removeProviderChangeListener(this);
-            mHandler.removeCallbacks(this);
-
-            if (!mRefreshPending) {
-                return;
-            }
-
-            mRefreshPending = false;
-
-            ArrayList<PendingAppWidgetHostView> views = new ArrayList<>(mInfos.size());
-            mapOverItems((info, view) -> {
-                if (view instanceof PendingAppWidgetHostView && mInfos.contains(info)) {
-                    views.add((PendingAppWidgetHostView) view);
-                }
-                // process all children
-                return false;
-            });
-            for (PendingAppWidgetHostView view : views) {
-                view.reInflate();
-            }
-        }
-
-        @Override
-        public void notifyWidgetProvidersChanged() {
-            run();
-        }
-    }
-
     private class StateTransitionListener extends AnimatorListenerAdapter
             implements AnimatorUpdateListener {
 
diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java
index 9afe06c..d5a4022 100644
--- a/src/com/android/launcher3/allapps/AllAppsStore.java
+++ b/src/com/android/launcher3/allapps/AllAppsStore.java
@@ -17,7 +17,6 @@
 
 import static com.android.launcher3.model.data.AppInfo.COMPONENT_KEY_COMPARATOR;
 import static com.android.launcher3.model.data.AppInfo.EMPTY_ARRAY;
-import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK;
 
 import android.content.Context;
 import android.os.UserHandle;
@@ -229,11 +228,7 @@
     public void updateProgressBar(AppInfo app) {
         updateAllIcons((child) -> {
             if (child.getTag() == app) {
-                if ((app.runtimeStatusFlags & FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) == 0) {
-                    child.applyFromApplicationInfo(app);
-                } else {
-                    child.applyProgressLevel();
-                }
+                child.applyFromApplicationInfo(app);
             }
         });
     }
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
index 3464e9b..f189182 100644
--- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -24,7 +24,6 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.content.Context;
-import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Matrix;
 import android.graphics.Paint;
@@ -33,12 +32,14 @@
 import android.graphics.Rect;
 import android.util.Property;
 
+import androidx.annotation.VisibleForTesting;
 import androidx.core.graphics.ColorUtils;
 
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatedFloat;
 import com.android.launcher3.anim.AnimatorListeners;
+import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.FastBitmapDrawable;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.util.Themes;
@@ -63,8 +64,6 @@
 
     private static final int DEFAULT_PATH_SIZE = 100;
     private static final int MAX_PAINT_ALPHA = 255;
-    private static final int TRACK_ALPHA = (int) (0.27f * MAX_PAINT_ALPHA);
-    private static final int DISABLED_ICON_ALPHA = (int) (0.6f * MAX_PAINT_ALPHA);
 
     private static final long DURATION_SCALE = 500;
     private static final long SCALE_AND_ALPHA_ANIM_DURATION = 500;
@@ -284,20 +283,25 @@
                     (long) ((finalProgress - mInternalStateProgress) * DURATION_SCALE));
             mCurrentAnim.setInterpolator(LINEAR);
             if (isFinish) {
-                if (onFinishCallback != null) {
-                    mCurrentAnim.addListener(AnimatorListeners.forEndCallback(onFinishCallback));
-                }
                 mCurrentAnim.addListener(new AnimatorListenerAdapter() {
                     @Override
                     public void onAnimationEnd(Animator animation) {
                         mRanFinishAnimation = true;
                     }
                 });
+                if (onFinishCallback != null) {
+                    mCurrentAnim.addListener(AnimatorListeners.forEndCallback(onFinishCallback));
+                }
             }
             mCurrentAnim.start();
         }
     }
 
+    @VisibleForTesting
+    public ObjectAnimator getActiveAnimation() {
+        return mCurrentAnim;
+    }
+
     /**
      * Sets the internal progress and updates the UI accordingly
      *   for progress <= 0:
@@ -358,8 +362,7 @@
     @Override
     public FastBitmapConstantState newConstantState() {
         return new PreloadIconConstantState(
-                mBitmap,
-                mIconColor,
+                mBitmapInfo,
                 mItem,
                 mIndicatorColor,
                 new int[] {mSystemAccentColor, mSystemBackgroundColor},
@@ -377,14 +380,13 @@
         private final Path mShapePath;
 
         public PreloadIconConstantState(
-                Bitmap bitmap,
-                int iconColor,
+                BitmapInfo bitmapInfo,
                 ItemInfoWithIcon info,
                 int indicatorColor,
                 int[] preloadColors,
                 boolean isDarkMode,
                 Path shapePath) {
-            super(bitmap, iconColor);
+            super(bitmapInfo);
             mInfo = info;
             mIndicatorColor = indicatorColor;
             mPreloadColors = preloadColors;
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index a04cbfb..ddc775d 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -49,7 +49,6 @@
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.CollectionInfo;
 import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.shortcuts.ShortcutKey;
@@ -70,7 +69,6 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -419,9 +417,9 @@
          * Binds updated incremental download progress
          */
         default void bindIncrementalDownloadProgressUpdated(AppInfo app) { }
-        default void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) { }
-        default void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets) { }
-        default void bindRestoreItemsChange(HashSet<ItemInfo> updates) { }
+
+        /** Called when a runtime property of the ItemInfo is updated due to some system event */
+        default void bindItemsUpdated(Set<ItemInfo> updates) { }
         default void bindWorkspaceComponentsRemoved(Predicate<ItemInfo> matcher) { }
 
         /**
diff --git a/src/com/android/launcher3/model/CacheDataUpdatedTask.java b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
index b544b91..48934e2 100644
--- a/src/com/android/launcher3/model/CacheDataUpdatedTask.java
+++ b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
@@ -15,6 +15,9 @@
  */
 package com.android.launcher3.model;
 
+import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
+import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER;
+
 import android.content.ComponentName;
 import android.os.UserHandle;
 
@@ -23,6 +26,8 @@
 import com.android.launcher3.LauncherModel.ModelUpdateTask;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 
 import java.util.ArrayList;
@@ -55,7 +60,7 @@
     public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
             @NonNull AllAppsList apps) {
         IconCache iconCache = taskController.getApp().getIconCache();
-        ArrayList<WorkspaceItemInfo> updatedShortcuts = new ArrayList<>();
+        ArrayList<ItemInfo> updatedItems = new ArrayList<>();
 
         synchronized (dataModel) {
             dataModel.forAllWorkspaceItemInfos(mUser, si -> {
@@ -64,12 +69,25 @@
                         && isValidShortcut(si) && cn != null
                         && mPackages.contains(cn.getPackageName())) {
                     iconCache.getTitleAndIcon(si, si.getMatchingLookupFlag());
-                    updatedShortcuts.add(si);
+                    updatedItems.add(si);
                 }
             });
+
+            dataModel.itemsIdMap.stream()
+                    .filter(WIDGET_FILTER)
+                    .filter(item -> mUser.equals(item.user))
+                    .map(item -> (LauncherAppWidgetInfo) item)
+                    .filter(widget -> mPackages.contains(widget.providerName.getPackageName())
+                            && widget.pendingItemInfo != null)
+                    .forEach(widget -> {
+                        iconCache.getTitleAndIconForApp(
+                                widget.pendingItemInfo, DEFAULT_LOOKUP_FLAG);
+                        updatedItems.add(widget);
+                    });
+
             apps.updateIconsAndLabels(mPackages, mUser);
         }
-        taskController.bindUpdatedWorkspaceItems(updatedShortcuts);
+        taskController.bindUpdatedWorkspaceItems(updatedItems);
         taskController.bindApplicationsIfNeeded();
     }
 
diff --git a/src/com/android/launcher3/model/ModelTaskController.kt b/src/com/android/launcher3/model/ModelTaskController.kt
index fc53343..40ea17d 100644
--- a/src/com/android/launcher3/model/ModelTaskController.kt
+++ b/src/com/android/launcher3/model/ModelTaskController.kt
@@ -22,7 +22,6 @@
 import com.android.launcher3.celllayout.CellPosMapper
 import com.android.launcher3.model.BgDataModel.FixedContainerItems
 import com.android.launcher3.model.data.ItemInfo
-import com.android.launcher3.model.data.WorkspaceItemInfo
 import com.android.launcher3.util.PackageUserKey
 import com.android.launcher3.widget.model.WidgetsListBaseEntriesBuilder
 import java.util.Objects
@@ -51,18 +50,17 @@
      */
     fun getModelWriter() = model.getWriter(false /* verifyChanges */, CellPosMapper.DEFAULT, null)
 
-    fun bindUpdatedWorkspaceItems(allUpdates: List<WorkspaceItemInfo>) {
+    fun bindUpdatedWorkspaceItems(allUpdates: Collection<ItemInfo>) {
         // Bind workspace items
-        val workspaceUpdates =
-            allUpdates.stream().filter { info -> info.id != ItemInfo.NO_ID }.toList()
+        val workspaceUpdates = allUpdates.filter { it.id != ItemInfo.NO_ID }.toSet()
         if (workspaceUpdates.isNotEmpty()) {
-            scheduleCallbackTask { it.bindWorkspaceItemsChanged(workspaceUpdates) }
+            scheduleCallbackTask { it.bindItemsUpdated(workspaceUpdates) }
         }
 
         // Bind extra items if any
         allUpdates
             .stream()
-            .mapToInt { info: WorkspaceItemInfo -> info.container }
+            .mapToInt { it.container }
             .distinct()
             .mapToObj { dataModel.extraItems.get(it) }
             .filter { Objects.nonNull(it) }
diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
index 4103937..a216042 100644
--- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
+++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
@@ -99,8 +99,7 @@
                     });
 
             if (!updates.isEmpty()) {
-                taskController.scheduleCallbackTask(
-                        callbacks -> callbacks.bindRestoreItemsChange(updates));
+                taskController.bindUpdatedWorkspaceItems(updates);
             }
         }
     }
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 1153f48..6bef292 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -214,8 +214,7 @@
 
         // Update shortcut infos
         if (mOp == OP_ADD || flagOp != FlagOp.NO_OP) {
-            final ArrayList<WorkspaceItemInfo> updatedWorkspaceItems = new ArrayList<>();
-            final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<>();
+            final ArrayList<ItemInfo> updatedWorkspaceItems = new ArrayList<>();
 
             // For system apps, package manager send OP_UPDATE when an app is enabled.
             final boolean isNewApkAvailable = mOp == OP_ADD || mOp == OP_UPDATE;
@@ -364,8 +363,8 @@
                             // if the widget has a config activity. In case there is no config
                             // activity, it will be marked as 'restored' during bind.
                             widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
-
-                            widgets.add(widgetInfo);
+                            widgetInfo.installProgress = 100;
+                            updatedWorkspaceItems.add(widgetInfo);
                             taskController.getModelWriter().updateItemInDatabase(widgetInfo);
                         });
             }
@@ -377,10 +376,6 @@
                         "removing shortcuts with invalid target components."
                                 + " ids=" + removedShortcuts);
             }
-
-            if (!widgets.isEmpty()) {
-                taskController.scheduleCallbackTask(c -> c.bindWidgetsRestored(widgets));
-            }
         }
 
         final HashSet<String> removedPackages = new HashSet<>();
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 78709b8..381d17a 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -228,10 +228,9 @@
     private static void onClickPendingAppItem(View v, Launcher launcher, String packageName,
             boolean downloadStarted) {
         ItemInfo item = (ItemInfo) v.getTag();
-        CompletableFuture<SessionInfo> siFuture;
-        siFuture = CompletableFuture.supplyAsync(() ->
-                        InstallSessionHelper.INSTANCE.get(launcher)
-                                .getActiveSessionInfo(item.user, packageName),
+        CompletableFuture<SessionInfo> siFuture = CompletableFuture.supplyAsync(() ->
+                InstallSessionHelper.INSTANCE.get(launcher)
+                        .getActiveSessionInfo(item.user, packageName),
                 UI_HELPER_EXECUTOR);
         Consumer<SessionInfo> marketLaunchAction = sessionInfo -> {
             if (sessionInfo != null) {
@@ -245,8 +244,8 @@
                 }
             }
             // Fallback to using custom market intent.
-            Intent intent = ApiWrapper.INSTANCE.get(launcher).getAppMarketActivityIntent(
-                    packageName, Process.myUserHandle());
+            Intent intent = ApiWrapper.INSTANCE.get(launcher).getMarketSearchIntent(
+                    packageName, item.user);
             launcher.startActivitySafely(v, intent, item);
         };
 
@@ -358,9 +357,7 @@
         // Check for abandoned promise
         if ((v instanceof BubbleTextView) && shortcut.hasPromiseIconUi()
                 && (!Flags.enableSupportForArchiving() || !shortcut.isArchived())) {
-            String packageName = shortcut.getIntent().getComponent() != null
-                    ? shortcut.getIntent().getComponent().getPackageName()
-                    : shortcut.getIntent().getPackage();
+            String packageName = shortcut.getTargetPackage();
             if (!TextUtils.isEmpty(packageName)) {
                 onClickPendingAppItem(
                         v,
diff --git a/src/com/android/launcher3/util/ApiWrapper.java b/src/com/android/launcher3/util/ApiWrapper.java
index 467a7ec..d24d084 100644
--- a/src/com/android/launcher3/util/ApiWrapper.java
+++ b/src/com/android/launcher3/util/ApiWrapper.java
@@ -28,6 +28,7 @@
 import android.content.pm.ShortcutInfo;
 import android.graphics.drawable.ColorDrawable;
 import android.net.Uri;
+import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.ArrayMap;
@@ -120,6 +121,21 @@
      * Activity).
      */
     public Intent getAppMarketActivityIntent(String packageName, UserHandle user) {
+        return createMarketIntent(packageName);
+    }
+
+    /**
+     * Returns an intent which can be used to start a search for a package on app market
+     */
+    public Intent getMarketSearchIntent(String packageName, UserHandle user) {
+        // If we are search for the current user, just launch the market directly as the
+        // system won't have the installer details either
+        return  (Process.myUserHandle().equals(user))
+                ? createMarketIntent(packageName)
+                : getAppMarketActivityIntent(packageName, user);
+    }
+
+    private static Intent createMarketIntent(String packageName) {
         return new Intent(Intent.ACTION_VIEW)
                 .setData(new Uri.Builder()
                         .scheme("market")
diff --git a/src/com/android/launcher3/util/LauncherBindableItemsContainer.java b/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
index 02779ce..20e3eaf 100644
--- a/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
+++ b/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
@@ -15,24 +15,20 @@
  */
 package com.android.launcher3.util;
 
-import android.graphics.drawable.Drawable;
 import android.view.View;
 
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.apppairs.AppPairIcon;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.graphics.PreloadIconDrawable;
 import com.android.launcher3.model.data.AppPairInfo;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.widget.PendingAppWidgetHostView;
 
-import java.util.HashSet;
-import java.util.List;
+import java.util.Set;
 
 /**
  * Interface representing a container which can bind Launcher items with some utility methods
@@ -41,27 +37,22 @@
 
     /**
      * Called to update workspace items as a result of
-     * {@link com.android.launcher3.model.BgDataModel.Callbacks#bindWorkspaceItemsChanged(List)}
+     * {@link com.android.launcher3.model.BgDataModel.Callbacks#bindItemsUpdated(Set)}
      */
-    default void updateWorkspaceItems(List<WorkspaceItemInfo> shortcuts, ActivityContext context) {
-        final HashSet<WorkspaceItemInfo> updates = new HashSet<>(shortcuts);
+    default void updateContainerItems(Set<ItemInfo> updates, ActivityContext context) {
         ItemOperator op = (info, v) -> {
-            if (v instanceof BubbleTextView && updates.contains(info)) {
-                WorkspaceItemInfo si = (WorkspaceItemInfo) info;
-                BubbleTextView shortcut = (BubbleTextView) v;
-                Drawable oldIcon = shortcut.getIcon();
-                boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable)
-                        && ((PreloadIconDrawable) oldIcon).hasNotCompleted();
-                shortcut.applyFromWorkspaceItem(
-                        si,
-                        si.isPromise() != oldPromiseState
-                                && oldIcon instanceof PreloadIconDrawable
-                                ? (PreloadIconDrawable) oldIcon
-                                : null);
-            } else if (info instanceof FolderInfo && v instanceof FolderIcon) {
-                ((FolderIcon) v).updatePreviewItems(updates::contains);
+            if (v instanceof BubbleTextView shortcut
+                    && info instanceof WorkspaceItemInfo wii
+                    && updates.contains(info)) {
+                shortcut.applyFromWorkspaceItem(wii);
+            } else if (info instanceof FolderInfo && v instanceof FolderIcon folderIcon) {
+                folderIcon.updatePreviewItems(updates::contains);
             } else if (info instanceof AppPairInfo && v instanceof AppPairIcon appPairIcon) {
                 appPairIcon.maybeRedrawForWorkspaceUpdate(updates::contains);
+            } else if (v instanceof PendingAppWidgetHostView pendingView
+                    && updates.contains(info)) {
+                pendingView.applyState();
+                pendingView.postProviderAvailabilityCheck();
             }
 
             // Iterate all items
@@ -76,35 +67,6 @@
     }
 
     /**
-     * Called to update restored items as a result of
-     * {@link com.android.launcher3.model.BgDataModel.Callbacks#bindRestoreItemsChange(HashSet)}}
-     */
-    default void updateRestoreItems(final HashSet<ItemInfo> updates, ActivityContext context) {
-        ItemOperator op = (info, v) -> {
-            if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView
-                    && updates.contains(info)) {
-                ((BubbleTextView) v).applyLoadingState(null);
-            } else if (v instanceof PendingAppWidgetHostView
-                    && info instanceof LauncherAppWidgetInfo
-                    && updates.contains(info)) {
-                ((PendingAppWidgetHostView) v).applyState();
-            } else if (v instanceof FolderIcon && info instanceof FolderInfo) {
-                ((FolderIcon) v).updatePreviewItems(updates::contains);
-            } else if (info instanceof AppPairInfo && v instanceof AppPairIcon appPairIcon) {
-                appPairIcon.maybeRedrawForWorkspaceUpdate(updates::contains);
-            }
-            // process all the shortcuts
-            return false;
-        };
-
-        mapOverItems(op);
-        Folder folder = Folder.getOpen(context);
-        if (folder != null) {
-            folder.iterateOverItems(op);
-        }
-    }
-
-    /**
      * Map the operator over the shortcuts and widgets.
      *
      * @param op the operator to map over the shortcuts
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index 9c9b80d..cd8e457 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -21,7 +21,7 @@
 import static android.graphics.Paint.FILTER_BITMAP_FLAG;
 
 import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
-import static com.android.launcher3.icons.FastBitmapDrawable.getDisabledColorFilter;
+import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import android.appwidget.AppWidgetProviderInfo;
@@ -37,6 +37,9 @@
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
 import android.text.Layout;
 import android.text.StaticLayout;
 import android.text.TextPaint;
@@ -60,8 +63,10 @@
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.PackageItemInfo;
+import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.Themes;
+import com.android.launcher3.widget.LauncherWidgetHolder.ProviderChangedListener;
 
 import java.util.List;
 
@@ -81,6 +86,8 @@
     private final Matrix mMatrix = new Matrix();
     private final RectF mPreviewBitmapRect = new RectF();
     private final RectF mCanvasRect = new RectF();
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private final RunnableList mOnDetachCleanup = new RunnableList();
 
     private final LauncherWidgetHolder mWidgetHolder;
     private final LauncherAppWidgetProviderInfo mAppwidget;
@@ -90,7 +97,6 @@
     private final CharSequence mLabel;
 
     private OnClickListener mClickListener;
-    private SafeCloseable mOnDetachCleanup;
 
     private int mDragFlags;
 
@@ -210,16 +216,15 @@
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
 
+        mOnDetachCleanup.executeAllAndClear();
         if ((mAppwidget != null)
                 && !mInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)
                 && mInfo.restoreStatus != LauncherAppWidgetInfo.RESTORE_COMPLETED) {
             // If the widget is not completely restored, but has a valid ID, then listen of
             // updates from provider app for potential restore complete.
-            if (mOnDetachCleanup != null) {
-                mOnDetachCleanup.close();
-            }
-            mOnDetachCleanup = mWidgetHolder.addOnUpdateListener(
+            SafeCloseable updateCleanup = mWidgetHolder.addOnUpdateListener(
                     mInfo.appWidgetId, mAppwidget, this::checkIfRestored);
+            mOnDetachCleanup.add(updateCleanup::close);
             checkIfRestored();
         }
     }
@@ -227,10 +232,7 @@
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        if (mOnDetachCleanup != null) {
-            mOnDetachCleanup.close();
-            mOnDetachCleanup = null;
-        }
+        mOnDetachCleanup.executeAllAndClear();
     }
 
     /**
@@ -295,43 +297,30 @@
             mCenterDrawable.setCallback(null);
             mCenterDrawable = null;
         }
-        mDragFlags = 0;
-        if (info.bitmap.icon != null) {
-            mDragFlags = FLAG_DRAW_ICON;
+        mDragFlags = FLAG_DRAW_ICON;
 
-            Drawable widgetCategoryIcon = getWidgetCategoryIcon();
-            // The view displays three modes,
-            //   1) App icon in the center
-            //   2) Preload icon in the center
-            //   3) App icon in the center with a setup icon on the top left corner.
-            if (mDisabledForSafeMode) {
-                if (widgetCategoryIcon == null) {
-                    FastBitmapDrawable disabledIcon = info.newIcon(getContext());
-                    disabledIcon.setIsDisabled(true);
-                    mCenterDrawable = disabledIcon;
-                } else {
-                    widgetCategoryIcon.setColorFilter(getDisabledColorFilter());
-                    mCenterDrawable = widgetCategoryIcon;
-                }
-                mSettingIconDrawable = null;
-            } else if (isReadyForClickSetup()) {
-                mCenterDrawable = widgetCategoryIcon == null
-                        ? info.newIcon(getContext())
-                        : widgetCategoryIcon;
-                mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate();
-                updateSettingColor(info.bitmap.color);
+        // The view displays three modes,
+        //   1) App icon in the center
+        //   2) Preload icon in the center
+        //   3) App icon in the center with a setup icon on the top left corner.
+        if (mDisabledForSafeMode) {
+            FastBitmapDrawable disabledIcon = info.newIcon(getContext());
+            disabledIcon.setIsDisabled(true);
+            mCenterDrawable = disabledIcon;
+            mSettingIconDrawable = null;
+        } else if (isReadyForClickSetup()) {
+            mCenterDrawable = info.newIcon(getContext());
+            mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate();
+            updateSettingColor(info.bitmap.color);
 
-                mDragFlags |= FLAG_DRAW_SETTINGS | FLAG_DRAW_LABEL;
-            } else {
-                mCenterDrawable = widgetCategoryIcon == null
-                        ? newPendingIcon(getContext(), info)
-                        : widgetCategoryIcon;
-                mSettingIconDrawable = null;
-                applyState();
-            }
-            mCenterDrawable.setCallback(this);
-            mDrawableSizeChanged = true;
+            mDragFlags |= FLAG_DRAW_SETTINGS | FLAG_DRAW_LABEL;
+        } else {
+            mCenterDrawable = newPendingIcon(getContext(), info);
+            mSettingIconDrawable = null;
+            applyState();
         }
+        mCenterDrawable.setCallback(this);
+        mDrawableSizeChanged = true;
         invalidate();
     }
 
@@ -350,6 +339,11 @@
     }
 
     public void applyState() {
+        if (mCenterDrawable instanceof FastBitmapDrawable fb
+                && mInfo.pendingItemInfo != null
+                && !fb.isSameInfo(mInfo.pendingItemInfo.bitmap)) {
+            reapplyItemInfo(mInfo.pendingItemInfo);
+        }
         if (mCenterDrawable != null) {
             mCenterDrawable.setLevel(Math.max(mInfo.installProgress, 0));
         }
@@ -486,16 +480,72 @@
     }
 
     /**
-     * Returns the widget category icon for {@link #mInfo}.
-     *
-     * <p>If {@link #mInfo}'s category is {@code PackageItemInfo#NO_CATEGORY} or unknown, returns
-     * {@code null}.
+     * Creates a runnable runnable which tries to refresh the widget if it is restored
      */
-    @Nullable
-    private Drawable getWidgetCategoryIcon() {
-        if (mInfo.pendingItemInfo.widgetCategory == WidgetSections.NO_CATEGORY) {
-            return null;
+    public void postProviderAvailabilityCheck() {
+        if (!mInfo.hasRestoreFlag(FLAG_PROVIDER_NOT_READY) && getAppWidgetInfo() == null) {
+            // If the info state suggests that the provider is ready, but there is no
+            // provider info attached on this pending view, recreate when the provider is available
+            DeferredWidgetRefresh restoreRunnable = new DeferredWidgetRefresh();
+            mOnDetachCleanup.add(restoreRunnable::cleanup);
+            mHandler.post(restoreRunnable::notifyWidgetProvidersChanged);
         }
-        return mInfo.pendingItemInfo.newIcon(getContext());
+    }
+
+    /**
+     * Used as a workaround to ensure that the AppWidgetService receives the
+     * PACKAGE_ADDED broadcast before updating widgets.
+     *
+     * This class will periodically check for the availability of the WidgetProvider as a result
+     * of providerChanged callback from the host. When the provider is available or a timeout of
+     * 10-sec is reached, it reinflates the pending-widget which in-turn goes through the process
+     * of re-evaluating the pending state of the widget,
+     */
+    private class DeferredWidgetRefresh implements Runnable, ProviderChangedListener {
+        private boolean mRefreshPending = true;
+
+        DeferredWidgetRefresh() {
+            mWidgetHolder.addProviderChangeListener(this);
+            // Force refresh after 10 seconds, if we don't get the provider changed event.
+            // This could happen when the provider is no longer available in the app.
+            Message msg = Message.obtain(getHandler(), this);
+            msg.obj = DeferredWidgetRefresh.class;
+            mHandler.sendMessageDelayed(msg, 10000);
+        }
+
+        /**
+         * Reinflate the widget if it is still attached.
+         */
+        @Override
+        public void run() {
+            cleanup();
+            if (mRefreshPending) {
+                reInflate();
+                mRefreshPending = false;
+            }
+        }
+
+        @Override
+        public void notifyWidgetProvidersChanged() {
+            final AppWidgetProviderInfo widgetInfo;
+            WidgetManagerHelper widgetHelper = new WidgetManagerHelper(getContext());
+            if (mInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
+                widgetInfo = widgetHelper.findProvider(mInfo.providerName, mInfo.user);
+            } else {
+                widgetInfo = widgetHelper.getLauncherAppWidgetInfo(mInfo.appWidgetId,
+                        mInfo.getTargetComponent());
+            }
+            if (widgetInfo != null) {
+                run();
+            }
+        }
+
+        /**
+         * Removes any scheduled callbacks and change listeners, no-op if nothing is scheduled
+         */
+        public void cleanup() {
+            mWidgetHolder.removeProviderChangeListener(this);
+            mHandler.removeCallbacks(this);
+        }
     }
 }
diff --git a/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java b/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java
index f51871b..faf6b91 100644
--- a/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java
@@ -27,7 +27,9 @@
 import static com.android.launcher3.Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING;
 import static com.android.launcher3.Flags.FLAG_USE_NEW_ICON_FOR_ARCHIVED_APPS;
 import static com.android.launcher3.LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE;
+import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -39,6 +41,8 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
 import android.graphics.Typeface;
 import android.os.Build;
 import android.os.UserHandle;
@@ -57,13 +61,17 @@
 import com.android.launcher3.Flags;
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.graphics.PreloadIconDrawable;
+import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.pm.PackageInstallInfo;
 import com.android.launcher3.search.StringMatcherUtility;
 import com.android.launcher3.util.ActivityContextWrapper;
 import com.android.launcher3.util.FlagOp;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext;
+import com.android.launcher3.util.TestUtil;
 import com.android.launcher3.views.BaseDragLayer;
 
 import org.junit.After;
@@ -73,6 +81,8 @@
 import org.junit.runner.RunWith;
 import org.mockito.MockitoAnnotations;
 
+import java.util.concurrent.CountDownLatch;
+
 /**
  * Unit tests for testing modifyTitleToSupportMultiLine() in BubbleTextView.java
  * This class tests a couple of strings and uses the getMaxLines() to determine if the test passes.
@@ -485,4 +495,40 @@
 
         assertThat(mBubbleTextView.getIcon().hasBadge()).isEqualTo(true);
     }
+
+    @Test
+    public void applyingPendingIcon_preserves_last_icon() throws Exception {
+        mItemInfoWithIcon.bitmap =
+                BitmapInfo.fromBitmap(Bitmap.createBitmap(100, 100, Config.ARGB_8888));
+        mItemInfoWithIcon.setProgressLevel(30, PackageInstallInfo.STATUS_INSTALLING);
+
+        TestUtil.runOnExecutorSync(MAIN_EXECUTOR,
+                () -> mBubbleTextView.applyIconAndLabel(mItemInfoWithIcon));
+        assertThat(mBubbleTextView.getIcon()).isInstanceOf(PreloadIconDrawable.class);
+        assertThat(mBubbleTextView.getIcon().getLevel()).isEqualTo(30);
+        PreloadIconDrawable oldIcon = (PreloadIconDrawable) mBubbleTextView.getIcon();
+
+        // Same icon is used when progress changes
+        mItemInfoWithIcon.setProgressLevel(50, PackageInstallInfo.STATUS_INSTALLING);
+        TestUtil.runOnExecutorSync(MAIN_EXECUTOR,
+                () -> mBubbleTextView.applyIconAndLabel(mItemInfoWithIcon));
+        assertThat(mBubbleTextView.getIcon()).isSameInstanceAs(oldIcon);
+        assertThat(mBubbleTextView.getIcon().getLevel()).isEqualTo(50);
+
+        // Icon is replaced with a non pending icon when download finishes
+        mItemInfoWithIcon.setProgressLevel(100, PackageInstallInfo.STATUS_INSTALLED);
+
+        CountDownLatch animWait = new CountDownLatch(1);
+        TestUtil.runOnExecutorSync(MAIN_EXECUTOR, () -> {
+            mBubbleTextView.applyIconAndLabel(mItemInfoWithIcon);
+            assertThat(mBubbleTextView.getIcon()).isSameInstanceAs(oldIcon);
+            assertThat(oldIcon.getActiveAnimation()).isNotNull();
+            oldIcon.getActiveAnimation().addListener(forEndCallback(animWait::countDown));
+        });
+        animWait.await();
+
+        // Assert that the icon is replaced with a non-pending icon
+        assertThat(mBubbleTextView.getIcon()).isNotInstanceOf(PreloadIconDrawable.class);
+    }
+
 }
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/LauncherBindableItemsContainerTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/LauncherBindableItemsContainerTest.kt
new file mode 100644
index 0000000..93be5f5
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/LauncherBindableItemsContainerTest.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2025 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.util
+
+import android.content.ComponentName
+import android.content.pm.LauncherApps
+import android.graphics.Bitmap
+import android.graphics.Bitmap.Config.ARGB_8888
+import android.os.Process.myUserHandle
+import android.platform.uiautomatorhelpers.DeviceHelpers.context
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.BubbleTextView
+import com.android.launcher3.graphics.PreloadIconDrawable
+import com.android.launcher3.icons.BitmapInfo
+import com.android.launcher3.icons.FastBitmapDrawable
+import com.android.launcher3.icons.PlaceHolderIconDrawable
+import com.android.launcher3.model.data.AppInfo
+import com.android.launcher3.model.data.AppInfo.makeLaunchIntent
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.pm.PackageInstallInfo
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator
+import com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY
+import com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY2
+import com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY3
+import com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LauncherBindableItemsContainerTest {
+
+    private val icon1 by lazy { getLAI(TEST_ACTIVITY) }
+    private val icon2 by lazy { getLAI(TEST_ACTIVITY2) }
+    private val icon3 by lazy { getLAI(TEST_ACTIVITY3) }
+
+    private val container = TestContainer()
+
+    @Test
+    fun `icon bitmap is updated`() {
+        container.addIcon(icon1)
+        container.addIcon(icon2)
+        container.addIcon(icon3)
+
+        assertThat(container.getAppIcon(icon1).icon)
+            .isInstanceOf(PlaceHolderIconDrawable::class.java)
+        assertThat(container.getAppIcon(icon2).icon)
+            .isInstanceOf(PlaceHolderIconDrawable::class.java)
+        assertThat(container.getAppIcon(icon3).icon)
+            .isInstanceOf(PlaceHolderIconDrawable::class.java)
+
+        icon2.bitmap = BitmapInfo.fromBitmap(Bitmap.createBitmap(200, 200, ARGB_8888))
+        TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {
+            container.updateContainerItems(setOf(icon2), container)
+        }
+
+        assertThat(container.getAppIcon(icon1).icon)
+            .isInstanceOf(PlaceHolderIconDrawable::class.java)
+        assertThat(container.getAppIcon(icon3).icon)
+            .isInstanceOf(PlaceHolderIconDrawable::class.java)
+        assertThat(container.getAppIcon(icon2).icon)
+            .isNotInstanceOf(PlaceHolderIconDrawable::class.java)
+        assertThat(container.getAppIcon(icon2).icon).isInstanceOf(FastBitmapDrawable::class.java)
+    }
+
+    @Test
+    fun `icon download progress updated`() {
+        container.addIcon(icon1)
+        container.addIcon(icon2)
+        assertThat(container.getAppIcon(icon1).icon)
+            .isInstanceOf(PlaceHolderIconDrawable::class.java)
+        assertThat(container.getAppIcon(icon2).icon)
+            .isInstanceOf(PlaceHolderIconDrawable::class.java)
+
+        icon1.status = WorkspaceItemInfo.FLAG_RESTORED_ICON
+        icon1.bitmap = BitmapInfo.fromBitmap(Bitmap.createBitmap(200, 200, ARGB_8888))
+        icon1.setProgressLevel(30, PackageInstallInfo.STATUS_INSTALLING)
+        TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {
+            container.updateContainerItems(setOf(icon1), container)
+        }
+
+        assertThat(container.getAppIcon(icon2).icon)
+            .isInstanceOf(PlaceHolderIconDrawable::class.java)
+        assertThat(container.getAppIcon(icon1).icon).isInstanceOf(PreloadIconDrawable::class.java)
+        val oldIcon = container.getAppIcon(icon1).icon as PreloadIconDrawable
+        assertThat(oldIcon.level).isEqualTo(30)
+    }
+
+    private fun getLAI(className: String): WorkspaceItemInfo =
+        AppInfo(
+                context,
+                context
+                    .getSystemService(LauncherApps::class.java)!!
+                    .resolveActivity(
+                        makeLaunchIntent(ComponentName(TEST_PACKAGE, className)),
+                        myUserHandle(),
+                    )!!,
+                myUserHandle(),
+            )
+            .makeWorkspaceItem(context)
+
+    class TestContainer : ActivityContextWrapper(context), LauncherBindableItemsContainer {
+
+        val items = mutableMapOf<ItemInfo, View>()
+
+        override fun mapOverItems(op: ItemOperator) {
+            items.forEach { (item, view) -> if (op.evaluate(item, view)) return@forEach }
+        }
+
+        fun addIcon(info: WorkspaceItemInfo) {
+            val btv = BubbleTextView(this)
+            btv.applyFromWorkspaceItem(info)
+            items[info] = btv
+        }
+
+        fun getAppIcon(info: WorkspaceItemInfo) = items[info] as BubbleTextView
+    }
+}