Updating the ui for widget restore flow

> Pending widget show a PreloadIconDrawable to indicate
installation progress
> Only the concerned widgets are reinflated on package
install and not the whole workspace.
> Adding support for storing default package icon in
IconCache

issue: 10779035
issue: 16737660

Change-Id: Id787ae4a5ef72d6e01aeb5a1bae5ab8840037679
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 221df58..06b7775 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -16,22 +16,20 @@
 
 package com.android.launcher3;
 
-import com.android.launcher3.backup.BackupProtos;
-
 import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
-import android.graphics.Paint;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.util.Log;
 
 import com.android.launcher3.compat.LauncherActivityInfoCompat;
@@ -54,12 +52,15 @@
  * Cache of application icons.  Icons can be made from any thread.
  */
 public class IconCache {
-    @SuppressWarnings("unused")
+
     private static final String TAG = "Launcher.IconCache";
 
     private static final int INITIAL_ICON_CACHE_CAPACITY = 50;
     private static final String RESOURCE_FILE_PREFIX = "icon_";
 
+    // Empty class name is used for storing package default entry.
+    private static final String EMPTY_CLASS_NAME = ".";
+
     private static final boolean DEBUG = true;
 
     private static class CacheEntry {
@@ -237,7 +238,7 @@
             HashMap<Object, CharSequence> labelCache) {
         synchronized (mCache) {
             CacheEntry entry = cacheLocked(application.componentName, info, labelCache,
-                    info.getUser());
+                    info.getUser(), false);
 
             application.title = entry.title;
             application.iconBitmap = entry.icon;
@@ -246,10 +247,10 @@
     }
 
     public Bitmap getIcon(Intent intent, UserHandleCompat user) {
-        return getIcon(intent, null, user);
+        return getIcon(intent, null, user, true);
     }
 
-    public Bitmap getIcon(Intent intent, String title, UserHandleCompat user) {
+    public Bitmap getIcon(Intent intent, String title, UserHandleCompat user, boolean usePkgIcon) {
         synchronized (mCache) {
             LauncherActivityInfoCompat launcherActInfo =
                     mLauncherApps.resolveActivity(intent, user);
@@ -257,11 +258,11 @@
 
             // null info means not installed, but if we have a component from the intent then
             // we should still look in the cache for restored app icons.
-            if (launcherActInfo == null && component == null) {
+            if (component == null) {
                 return getDefaultIcon(user);
             }
 
-            CacheEntry entry = cacheLocked(component, launcherActInfo, null, user);
+            CacheEntry entry = cacheLocked(component, launcherActInfo, null, user, usePkgIcon);
             if (title != null) {
                 entry.title = title;
                 entry.contentDescription = mUserManager.getBadgedLabelForUser(title, user);
@@ -284,7 +285,7 @@
                 return null;
             }
 
-            CacheEntry entry = cacheLocked(component, info, labelCache, info.getUser());
+            CacheEntry entry = cacheLocked(component, info, labelCache, info.getUser(), false);
             return entry.icon;
         }
     }
@@ -294,7 +295,7 @@
     }
 
     private CacheEntry cacheLocked(ComponentName componentName, LauncherActivityInfoCompat info,
-            HashMap<Object, CharSequence> labelCache, UserHandleCompat user) {
+            HashMap<Object, CharSequence> labelCache, UserHandleCompat user, boolean usePackageIcon) {
         CacheKey cacheKey = new CacheKey(componentName, user);
         CacheEntry entry = mCache.get(cacheKey);
         if (entry == null) {
@@ -324,15 +325,52 @@
                             componentName.toShortString());
                     entry.icon = preloaded;
                 } else {
-                    if (DEBUG) Log.d(TAG, "using default icon for " +
-                            componentName.toShortString());
-                    entry.icon = getDefaultIcon(user);
+                    if (usePackageIcon) {
+                        CacheEntry packageEntry = getEntryForPackage(
+                                componentName.getPackageName(), user);
+                        if (packageEntry != null && packageEntry.icon != null) {
+                            if (DEBUG) Log.d(TAG, "using package default icon for " +
+                                    componentName.toShortString());
+                            entry.icon = packageEntry.icon;
+                        }
+                    }
+                    if (entry.icon == null) {
+                        if (DEBUG) Log.d(TAG, "using default icon for " +
+                                componentName.toShortString());
+                        entry.icon = getDefaultIcon(user);
+                    }
                 }
             }
         }
         return entry;
     }
 
+    /**
+     * Gets an entry for the package, which can be used as a fallback entry for various components.
+     */
+    private CacheEntry getEntryForPackage(String packageName, UserHandleCompat user) {
+        ComponentName cn = getPackageComponent(packageName);
+        CacheKey cacheKey = new CacheKey(cn, user);
+        CacheEntry entry = mCache.get(cacheKey);
+        if (entry == null) {
+            entry = new CacheEntry();
+            mCache.put(cacheKey, entry);
+
+            try {
+                ApplicationInfo info = mPackageManager.getApplicationInfo(packageName, 0);
+                entry.title = info.loadLabel(mPackageManager);
+                entry.icon = Utilities.createIconBitmap(info.loadIcon(mPackageManager), mContext);
+            } catch (NameNotFoundException e) {
+                if (DEBUG) Log.d(TAG, "Application not installed " + packageName);
+            }
+
+            if (entry.icon == null) {
+                entry.icon = getPreloadedIcon(cn, user);
+            }
+        }
+        return entry;
+    }
+
     public HashMap<ComponentName,Bitmap> getAllIcons() {
         synchronized (mCache) {
             HashMap<ComponentName,Bitmap> set = new HashMap<ComponentName,Bitmap>();
@@ -471,4 +509,8 @@
         String filename = resourceName.replace(File.separatorChar, '_');
         return RESOURCE_FILE_PREFIX + filename;
     }
+
+    static ComponentName getPackageComponent(String packageName) {
+        return new ComponentName(packageName, EMPTY_CLASS_NAME);
+    }
 }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 5eedc8a..062e848 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -23,7 +23,6 @@
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.annotation.TargetApi;
 import android.app.Activity;
 import android.app.ActivityManager;
@@ -90,10 +89,8 @@
 import android.view.Window;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
-import android.view.animation.AccelerateDecelerateInterpolator;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.DecelerateInterpolator;
-import android.view.animation.LinearInterpolator;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Advanceable;
 import android.widget.FrameLayout;
@@ -4445,7 +4442,9 @@
             item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
         } else {
             appWidgetInfo = null;
-            item.hostView = new PendingAppWidgetHostView(this, item.restoreStatus);
+            PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item);
+            view.updateIcon(mIconCache);
+            item.hostView = view;
             item.hostView.updateAppWidget(null);
             item.hostView.setOnClickListener(this);
         }
@@ -4478,10 +4477,7 @@
             return;
         }
 
-        PendingAppWidgetHostView pendingView = (PendingAppWidgetHostView) view;
-        pendingView.setStatus(LauncherAppWidgetInfo.RESTORE_COMPLETED);
-
-        LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) pendingView.getTag();
+        LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag();
         info.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
 
         mWorkspace.reinflateWidgetsIfNecessary();
diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java
index fa5e38f..a309f26 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHost.java
@@ -22,6 +22,8 @@
 import android.content.Context;
 import android.os.TransactionTooLargeException;
 
+import java.util.ArrayList;
+
 /**
  * Specific {@link AppWidgetHost} that creates our {@link LauncherAppWidgetHostView}
  * which correctly captures all long-press events. This ensures that users can
@@ -29,6 +31,8 @@
  */
 public class LauncherAppWidgetHost extends AppWidgetHost {
 
+    private final ArrayList<Runnable> mProviderChangeListeners = new ArrayList<Runnable>();
+
     Launcher mLauncher;
 
     public LauncherAppWidgetHost(Launcher launcher, int hostId) {
@@ -64,9 +68,21 @@
         clearViews();
     }
 
+    public void addProviderChangeListener(Runnable callback) {
+        mProviderChangeListeners.add(callback);
+    }
+
+    public void removeProviderChangeListener(Runnable callback) {
+        mProviderChangeListeners.remove(callback);
+    }
+
     protected void onProvidersChanged() {
         // Once we get the message that widget packages are updated, we need to rebind items
         // in AppsCustomize accordingly.
         mLauncher.bindPackagesUpdated(LauncherModel.getSortedWidgetsAndShortcuts(mLauncher));
+
+        for (Runnable callback : mProviderChangeListeners) {
+            callback.run();
+        }
     }
 }
diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java
index c1535ab..4755482 100644
--- a/src/com/android/launcher3/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java
@@ -67,6 +67,11 @@
      */
     int restoreStatus;
 
+    /**
+     * Indicates the installation progress of the widget provider
+     */
+    int installProgress;
+
     private boolean mHasNotifiedInitialWidgetSizeChanged;
 
     /**
diff --git a/src/com/android/launcher3/LauncherBackupHelper.java b/src/com/android/launcher3/LauncherBackupHelper.java
index aecf9b0..64e82c7 100644
--- a/src/com/android/launcher3/LauncherBackupHelper.java
+++ b/src/com/android/launcher3/LauncherBackupHelper.java
@@ -692,6 +692,9 @@
                         .decodeByteArray(widget.icon.data, 0, widget.icon.data.length);
                 if (icon == null) {
                     Log.w(TAG, "failed to unpack widget icon for " + key.name);
+                } else {
+                    IconCache.preloadIcon(mContext, ComponentName.unflattenFromString(widget.provider),
+                            icon, widget.icon.dpi);
                 }
             }
 
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 109a700..4c9d1a7 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -3089,7 +3089,7 @@
         info.user = UserHandleCompat.myUserHandle();
         info.contentDescription = mUserManager.getBadgedLabelForUser(
                 info.title.toString(), info.user);
-        info.setIcon(mIconCache.getIcon(intent, info.title.toString(), info.user));
+        info.setIcon(mIconCache.getIcon(intent, info.title.toString(), info.user, false));
         info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
         info.restoredIntent = intent;
         info.wasPromise = true;
@@ -3378,7 +3378,7 @@
     /**
      * Attempts to find an AppWidgetProviderInfo that matches the given component.
      */
-    AppWidgetProviderInfo findAppWidgetProviderInfoWithComponent(Context context,
+    static AppWidgetProviderInfo findAppWidgetProviderInfoWithComponent(Context context,
             ComponentName component) {
         List<AppWidgetProviderInfo> widgets =
             AppWidgetManager.getInstance(context).getInstalledProviders();
diff --git a/src/com/android/launcher3/PendingAppWidgetHostView.java b/src/com/android/launcher3/PendingAppWidgetHostView.java
index 048e9f8..0401436 100644
--- a/src/com/android/launcher3/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/PendingAppWidgetHostView.java
@@ -1,21 +1,59 @@
+/*
+ * Copyright (C) 2014 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;
 
 import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources.Theme;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.view.View;
 import android.view.View.OnClickListener;
-import android.widget.TextView;
 
 public class PendingAppWidgetHostView extends LauncherAppWidgetHostView implements OnClickListener {
 
-    int mRestoreStatus;
+    private static Theme sPreloaderTheme;
 
-    private TextView mDefaultView;
+    private final Rect mRect = new Rect();
+    private View mDefaultView;
     private OnClickListener mClickListener;
+    private final LauncherAppWidgetInfo mInfo;
+    private final int mStartState;
+    private final Intent mIconLookupIntent;
 
-    public PendingAppWidgetHostView(Context context, int restoreStatus) {
+    private Bitmap mIcon;
+    private PreloadIconDrawable mDrawable;
+
+    private Drawable mCenterDrawable;
+    private Drawable mTopCornerDrawable;
+
+    private boolean mDrawableSizeChanged;
+
+    public PendingAppWidgetHostView(Context context, LauncherAppWidgetInfo info) {
         super(context);
-        mRestoreStatus = restoreStatus;
+        mInfo = info;
+        mStartState = info.restoreStatus;
+        mIconLookupIntent = new Intent().setComponent(info.providerName);
+
+        setBackgroundResource(R.drawable.quantum_panel_dark);
+        setWillNotDraw(false);
     }
 
     @Override
@@ -27,7 +65,7 @@
     @Override
     protected View getDefaultView() {
         if (mDefaultView == null) {
-            mDefaultView = (TextView) mInflater.inflate(R.layout.appwidget_not_ready, this, false);
+            mDefaultView = mInflater.inflate(R.layout.appwidget_not_ready, this, false);
             mDefaultView.setOnClickListener(this);
             applyState();
         }
@@ -39,26 +77,57 @@
         mClickListener = l;
     }
 
-    public void setStatus(int status) {
-        if (mRestoreStatus != status) {
-            mRestoreStatus = status;
-            applyState();
+    @Override
+    public boolean isReinflateRequired() {
+        // Re inflate is required any time the widget restore status changes
+        return mStartState != mInfo.restoreStatus;
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+        mDrawableSizeChanged = true;
+    }
+
+    public void updateIcon(IconCache cache) {
+        Bitmap icon = cache.getIcon(mIconLookupIntent, mInfo.user);
+        if (mIcon == icon) {
+            return;
+        }
+        mIcon = icon;
+        if (mDrawable != null) {
+            mDrawable.setCallback(null);
+            mDrawable = null;
+        }
+        if (mIcon != null) {
+            // The view displays two modes, one with a setup icon and another with a preload icon
+            // in the center.
+            if (isReadyForClickSetup()) {
+                mCenterDrawable = getResources().getDrawable(R.drawable.ic_setting);
+                mTopCornerDrawable = new FastBitmapDrawable(mIcon);
+            } else {
+                if (sPreloaderTheme == null) {
+                    sPreloaderTheme = getResources().newTheme();
+                    sPreloaderTheme.applyStyle(R.style.PreloadIcon, true);
+                }
+
+                FastBitmapDrawable drawable = Utilities.createIconDrawable(mIcon);
+                mDrawable = new PreloadIconDrawable(drawable, sPreloaderTheme);
+                mDrawable.setCallback(this);
+                applyState();
+            }
+            mDrawableSizeChanged = true;
         }
     }
 
     @Override
-    public boolean isReinflateRequired() {
-        // Re inflate is required if the the widget is restored.
-        return mRestoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED;
+    protected boolean verifyDrawable(Drawable who) {
+        return (who == mDrawable) || super.verifyDrawable(who);
     }
 
-    private void applyState() {
-        if (mDefaultView != null) {
-            if (isReadyForClickSetup()) {
-                mDefaultView.setText(R.string.gadget_setup_text);
-            } else {
-                mDefaultView.setText(R.string.gadget_pending_text);
-            }
+    public void applyState() {
+        if (mDrawable != null) {
+            mDrawable.setLevel(mInfo.installProgress);
         }
     }
 
@@ -72,7 +141,51 @@
     }
 
     public boolean isReadyForClickSetup() {
-        return (mRestoreStatus & LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0
-                && (mRestoreStatus & LauncherAppWidgetInfo.FLAG_UI_NOT_READY) != 0;
+        return (mInfo.restoreStatus & LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0
+                && (mInfo.restoreStatus & LauncherAppWidgetInfo.FLAG_UI_NOT_READY) != 0;
     }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        if (mDrawable != null) {
+            if (mDrawableSizeChanged) {
+                int maxSize = LauncherAppState.getInstance().getDynamicGrid()
+                        .getDeviceProfile().iconSizePx + 2 * mDrawable.getOutset();
+                int size = Math.min(maxSize, Math.min(
+                        getWidth() - getPaddingLeft() - getPaddingRight(),
+                        getHeight() - getPaddingTop() - getPaddingBottom()));
+
+                mRect.set(0, 0, size, size);
+                mRect.inset(mDrawable.getOutset(), mDrawable.getOutset());
+                mRect.offsetTo((getWidth() - mRect.width()) / 2, (getHeight() - mRect.height()) / 2);
+                mDrawable.setBounds(mRect);
+                mDrawableSizeChanged = false;
+            }
+
+            mDrawable.draw(canvas);
+        } else if ((mCenterDrawable != null) && (mTopCornerDrawable != null)) {
+            if (mDrawableSizeChanged) {
+                int iconSize = getResources().getDimensionPixelSize(R.dimen.app_icon_size);
+                int paddingTop = getPaddingTop();
+                int paddingLeft = getPaddingLeft();
+
+                int size = Math.min(iconSize, Math.min(
+                        getWidth() - paddingLeft - getPaddingRight(),
+                        getHeight() - paddingTop - getPaddingBottom()));
+                mRect.set(0, 0, size, size);
+                mRect.offsetTo((getWidth() - mRect.width()) / 2, (getHeight() - mRect.height()) / 2);
+                mCenterDrawable.setBounds(mRect);
+
+                size = Math.min(size / 2,
+                        Math.max(mRect.top - paddingTop, mRect.left - paddingLeft));
+                mTopCornerDrawable.setBounds(paddingLeft, paddingTop,
+                        paddingLeft + size, paddingTop + size);
+                mDrawableSizeChanged = false;
+            }
+
+            mCenterDrawable.draw(canvas);
+            mTopCornerDrawable.draw(canvas);
+        }
+    }
+
 }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index cd6fcca..8b3a514 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -27,6 +27,7 @@
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.app.WallpaperManager;
+import android.appwidget.AppWidgetHost;
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
@@ -45,7 +46,10 @@
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Handler.Callback;
 import android.os.IBinder;
+import android.os.Message;
 import android.os.Parcelable;
 import android.support.v4.view.ViewCompat;
 import android.util.AttributeSet;
@@ -74,6 +78,7 @@
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * The workspace is a wide area with a wallpaper and a finite number of pages.
@@ -431,7 +436,6 @@
      * Initializes various states for this workspace.
      */
     protected void initWorkspace() {
-        Context context = getContext();
         mCurrentPage = mDefaultPage;
         Launcher.setScreen(mCurrentPage);
         LauncherAppState app = LauncherAppState.getInstance();
@@ -4887,7 +4891,14 @@
                         ((ShortcutInfo) info).setProgress(installInfo.progress);
                         ((ShortcutInfo) info).setState(installInfo.state);
                         ((BubbleTextView)v).applyState();
+                    } else if (v instanceof PendingAppWidgetHostView
+                            && info instanceof LauncherAppWidgetInfo
+                            && ((LauncherAppWidgetInfo) info).providerName.getPackageName()
+                                .equals(installInfo.packageName)) {
+                        ((LauncherAppWidgetInfo) info).installProgress = installInfo.progress;
+                        ((PendingAppWidgetHostView) v).applyState();
                     }
+
                     // process all the shortcuts
                     return false;
                 }
@@ -4904,7 +4915,8 @@
     }
 
     private void restorePendingWidgets(final Set<String> installedPackaged) {
-        final AtomicBoolean widgetsChanged = new AtomicBoolean(false);
+        final ArrayList<LauncherAppWidgetInfo> changedInfo = new ArrayList<LauncherAppWidgetInfo>();
+
         // Iterate non recursively as widgets can't be inside a folder.
         mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
 
@@ -4914,18 +4926,28 @@
                     LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info;
                     if (widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
                             && installedPackaged.contains(widgetInfo.providerName.getPackageName())) {
-                        widgetsChanged.set(true);
+
+                        changedInfo.add(widgetInfo);
+
+                        // Remove the provider not ready flag
+                        widgetInfo.restoreStatus &= ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
+                        LauncherModel.updateItemInDatabase(getContext(), widgetInfo);
                     }
                 }
                 // process all the widget
                 return false;
             }
         });
-        if (widgetsChanged.get()) {
-            // Reload layout and update widget status
-            // TODO instead of full reload, just update the specific widgets
-            getContext().getContentResolver()
-                .notifyChange(LauncherSettings.Favorites.CONTENT_URI, null);
+        if (!changedInfo.isEmpty()) {
+            DeferredWidgetRefresh widgetRefresh = new DeferredWidgetRefresh(changedInfo,
+                    mLauncher.getAppWidgetHost());
+            if (LauncherModel.findAppWidgetProviderInfoWithComponent(getContext(),
+                    changedInfo.get(0).providerName) != null) {
+                // Re-inflate the widgets which have changed status
+                widgetRefresh.run();
+            } else {
+                // widgetRefresh will automatically run when the packages are updated.
+            }
         }
     }
 
@@ -5003,4 +5025,53 @@
     public void getLocationInDragLayer(int[] loc) {
         mLauncher.getDragLayer().getLocationInDragLayer(this, loc);
     }
+
+    /**
+     * Used as a workaround to ensure that the AppWidgetService receives the
+     * PACKAGE_ADDED broadcast before updating widgets.
+     */
+    private class DeferredWidgetRefresh implements Runnable {
+        private final ArrayList<LauncherAppWidgetInfo> mInfos;
+        private final LauncherAppWidgetHost mHost;
+        private final Handler mHandler;
+
+        private boolean mRefreshPending;
+
+        public DeferredWidgetRefresh(ArrayList<LauncherAppWidgetInfo> infos,
+                LauncherAppWidgetHost host) {
+            mInfos = infos;
+            mHost = host;
+            mHandler = new Handler();
+            mRefreshPending = true;
+
+            mHost.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.
+            mHandler.postDelayed(this, 10000);
+        }
+
+        @Override
+        public void run() {
+            mHost.removeProviderChangeListener(this);
+            mHandler.removeCallbacks(this);
+
+            if (!mRefreshPending) {
+                return;
+            }
+
+            mRefreshPending = false;
+
+            for (LauncherAppWidgetInfo info : mInfos) {
+                if (info.hostView instanceof PendingAppWidgetHostView) {
+                    PendingAppWidgetHostView view = (PendingAppWidgetHostView) info.hostView;
+                    mLauncher.removeAppWidget(info);
+
+                    CellLayout cl = (CellLayout) view.getParent().getParent();
+                    // Remove the current widget
+                    cl.removeView(view);
+                    mLauncher.bindAppWidget(info);
+                }
+            }
+        }
+    }
 }