Handling label and icon from SessionInfo.

> The ShortcutInfo stores state as bit flags and not as ints
> Intents of auto-install shortcut are automatically updated
upon installation
> Icons/titles for active sessions are cached in IconCache

Change-Id: I2047849f67d4a8aaf2bc346b58110325bb4807d4
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index 4ea7b3d..931501c 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -298,7 +298,7 @@
                 return -1;
             }
 
-            mValues.put(Favorites.RESTORED, 1);
+            mValues.put(Favorites.RESTORED, ShortcutInfo.FLAG_AUTOINTALL_ICON);
             final Intent intent = new Intent(Intent.ACTION_MAIN, null)
                 .addCategory(Intent.CATEGORY_LAUNCHER)
                 .setComponent(new ComponentName(packageName, className))
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index d83f81d..a368796 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -26,7 +26,6 @@
 import android.graphics.Region;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.util.SparseArray;
 import android.util.TypedValue;
 import android.view.KeyEvent;
@@ -50,10 +49,6 @@
     private static final int SHADOW_SMALL_COLOUR = 0xCC000000;
     static final float PADDING_V = 3.0f;
 
-    private static final String TAG = "BubbleTextView";
-
-    private static final boolean DEBUG = false;
-
     private HolographicOutlineHelper mOutlineHelper;
     private Bitmap mPressedBackground;
 
@@ -118,6 +113,11 @@
 
     public void applyFromShortcutInfo(ShortcutInfo info, IconCache iconCache,
             boolean setDefaultPadding) {
+        applyFromShortcutInfo(info, iconCache, setDefaultPadding, false);
+    }
+
+    public void applyFromShortcutInfo(ShortcutInfo info, IconCache iconCache,
+            boolean setDefaultPadding, boolean promiseStateChanged) {
         Bitmap b = info.getIcon(iconCache);
         LauncherAppState app = LauncherAppState.getInstance();
 
@@ -135,8 +135,8 @@
         setText(info.title);
         setTag(info);
 
-        if (info.wasPromise) {
-            applyState();
+        if (promiseStateChanged || info.isPromise()) {
+            applyState(promiseStateChanged);
         }
     }
 
@@ -377,31 +377,13 @@
         mLongPressHelper.cancelLongPress();
     }
 
-    public void applyState() {
+    public void applyState(boolean promiseStateChanged) {
         if (getTag() instanceof ShortcutInfo) {
             ShortcutInfo info = (ShortcutInfo) getTag();
-            final int state = info.getState();
-
-            final int progressLevel;
-            if (DEBUG) Log.d(TAG, "applying icon state: " + state);
-
-            switch(state) {
-                case ShortcutInfo.PACKAGE_STATE_DEFAULT:
-                    progressLevel = 100;
-                    break;
-
-                case ShortcutInfo.PACKAGE_STATE_INSTALLING:
-                    setText(R.string.package_state_installing);
-                    progressLevel = info.getProgress();
-                    break;
-
-                case ShortcutInfo.PACKAGE_STATE_ERROR:
-                case ShortcutInfo.PACKAGE_STATE_UNKNOWN:
-                default:
-                    progressLevel = 0;
-                    setText(R.string.package_state_unknown);
-                    break;
-            }
+            final boolean isPromise = info.isPromise();
+            final int progressLevel = isPromise ?
+                    ((info.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE) ?
+                            info.getInstallProgress() : 0)) : 100;
 
             Drawable[] drawables = getCompoundDrawables();
             Drawable top = drawables[1];
@@ -415,12 +397,9 @@
                 }
 
                 preloadDrawable.setLevel(progressLevel);
-                if ((state == ShortcutInfo.PACKAGE_STATE_DEFAULT) && info.wasPromise) {
-                    // Clear the promise flag as it is no longer different than a normal shortcut,
-                    // once the animation has been run.
-                    info.wasPromise = !preloadDrawable.maybePerformFinishedAnimation();
+                if (promiseStateChanged) {
+                    preloadDrawable.maybePerformFinishedAnimation();
                 }
-
             }
         }
     }
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 76a85ca..75be836 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -30,6 +30,7 @@
 import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.launcher3.compat.LauncherActivityInfoCompat;
@@ -254,18 +255,16 @@
         return getIcon(intent, null, user, true);
     }
 
-    public Bitmap getIcon(Intent intent, String title, UserHandleCompat user, boolean usePkgIcon) {
+    private Bitmap getIcon(Intent intent, String title, UserHandleCompat user, boolean usePkgIcon) {
         synchronized (mCache) {
-            LauncherActivityInfoCompat launcherActInfo =
-                    mLauncherApps.resolveActivity(intent, user);
             ComponentName component = intent.getComponent();
-
             // 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 (component == null) {
                 return getDefaultIcon(user);
             }
 
+            LauncherActivityInfoCompat launcherActInfo = mLauncherApps.resolveActivity(intent, user);
             CacheEntry entry = cacheLocked(component, launcherActInfo, null, user, usePkgIcon);
             if (title != null) {
                 entry.title = title;
@@ -275,6 +274,32 @@
         }
     }
 
+    /**
+     * Fill in "shortcutInfo" with the icon and label for "info."
+     */
+    public void getTitleAndIcon(ShortcutInfo shortcutInfo, Intent intent, UserHandleCompat user,
+            boolean usePkgIcon) {
+        synchronized (mCache) {
+            ComponentName component = intent.getComponent();
+            // 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 (component == null) {
+                shortcutInfo.setIcon(getDefaultIcon(user));
+                shortcutInfo.title = "";
+                shortcutInfo.usingFallbackIcon = true;
+            } else {
+                LauncherActivityInfoCompat launcherActInfo =
+                        mLauncherApps.resolveActivity(intent, user);
+                CacheEntry entry = cacheLocked(component, launcherActInfo, null, user, usePkgIcon);
+
+                shortcutInfo.setIcon(entry.icon);
+                shortcutInfo.title = entry.title;
+                shortcutInfo.usingFallbackIcon = isDefaultIcon(entry.icon, user);
+            }
+        }
+    }
+
+
     public Bitmap getDefaultIcon(UserHandleCompat user) {
         if (!mDefaultIcons.containsKey(user)) {
             mDefaultIcons.put(user, makeDefaultIcon(user));
@@ -332,10 +357,11 @@
                     if (usePackageIcon) {
                         CacheEntry packageEntry = getEntryForPackage(
                                 componentName.getPackageName(), user);
-                        if (packageEntry != null && packageEntry.icon != null) {
+                        if (packageEntry != null) {
                             if (DEBUG) Log.d(TAG, "using package default icon for " +
                                     componentName.toShortString());
                             entry.icon = packageEntry.icon;
+                            entry.title = packageEntry.title;
                         }
                     }
                     if (entry.icon == null) {
@@ -350,6 +376,21 @@
     }
 
     /**
+     * Adds a default package entry in the cache. This entry is not persisted and will be removed
+     * when the cache is flushed.
+     */
+    public void cachePackageInstallInfo(String packageName, UserHandleCompat user,
+            Bitmap icon, CharSequence title) {
+        CacheEntry entry = getEntryForPackage(packageName, user);
+        if (!TextUtils.isEmpty(title)) {
+            entry.title = title;
+        }
+        if (icon != null) {
+            entry.icon = Utilities.createIconBitmap(icon, mContext);
+        }
+    }
+
+    /**
      * Gets an entry for the package, which can be used as a fallback entry for various components.
      */
     private CacheEntry getEntryForPackage(String packageName, UserHandleCompat user) {
@@ -358,6 +399,7 @@
         CacheEntry entry = mCache.get(cacheKey);
         if (entry == null) {
             entry = new CacheEntry();
+            entry.title = "";
             mCache.put(cacheKey, entry);
 
             try {
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index 8f96f74..09b77f7 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -146,10 +146,6 @@
         throw new RuntimeException("Unexpected Intent");
     }
 
-    protected Intent getRestoredIntent() {
-        throw new RuntimeException("Unexpected Intent");
-    }
-
     /**
      * Write the fields of this item to the DB
      * 
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 83f813d..32fed34 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -2600,9 +2600,11 @@
         }
 
         // Check for abandoned promise
-        if (shortcut.isAbandoned() && v instanceof BubbleTextView) {
+        if ((v instanceof BubbleTextView)
+                && shortcut.isPromise()
+                && !shortcut.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE)) {
             showBrokenAppInstallDialog(
-                    shortcut.getRestoredIntent().getComponent().getPackageName(),
+                    shortcut.getTargetComponent().getPackageName(),
                     new DialogInterface.OnClickListener() {
                         public void onClick(DialogInterface dialog, int id) {
                             startAppShortcutOrInfoActivity(v);
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 4ab4e4b..2657b6e 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -161,7 +161,7 @@
         return mModel;
     }
 
-    IconCache getIconCache() {
+    public IconCache getIconCache() {
         return mIconCache;
     }
 
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 5e8e2ad..b44433d 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -54,12 +54,14 @@
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.LauncherActivityInfoCompat;
 import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.PackageInstallerCompat;
 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 
 import java.lang.ref.WeakReference;
 import java.net.URISyntaxException;
+import java.security.InvalidParameterException;
 import java.text.Collator;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -359,7 +361,7 @@
         Iterator<AppInfo> iter = allAppsApps.iterator();
         while (iter.hasNext()) {
             ItemInfo a = iter.next();
-            if (LauncherModel.appWasRestored(ctx, a.getIntent(), a.user)) {
+            if (LauncherModel.appWasPromise(ctx, a.getIntent(), a.user)) {
                 restoredAppsFinal.add((AppInfo) a);
             }
         }
@@ -428,7 +430,7 @@
                         if (LauncherModel.shortcutExists(context, name, launchIntent)) {
                             // Only InstallShortcutReceiver sends us shortcutInfos, ignore them
                             if (a instanceof AppInfo &&
-                                    LauncherModel.appWasRestored(context, launchIntent, a.user)) {
+                                    LauncherModel.appWasPromise(context, launchIntent, a.user)) {
                                 restoredAppsFinal.add((AppInfo) a);
                             }
                             continue;
@@ -884,33 +886,14 @@
     }
 
     /**
-     * Returns true if the shortcuts already exists in the database.
-     * we identify a shortcut by the component name of the intent
-     * and the user.
+     * Returns true if the promise shortcuts with the same package name exists on the workspace.
      */
-    static boolean appWasRestored(Context context, Intent intent, UserHandleCompat user) {
-        final ContentResolver cr = context.getContentResolver();
+    static boolean appWasPromise(Context context, Intent intent, UserHandleCompat user) {
         final ComponentName component = intent.getComponent();
         if (component == null) {
             return false;
         }
-        String componentName = component.flattenToString();
-        String shortName = component.flattenToShortString();
-        long serialNumber = UserManagerCompat.getInstance(context)
-                .getSerialNumberForUser(user);
-        final String where = "(intent glob \"*component=" + componentName + "*\" or " +
-                "intent glob \"*component=" + shortName + "*\")" +
-                "and restored = 1 and profileId = " + serialNumber;
-        Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
-                new String[]{"intent", "restored", "profileId"}, where, null, null);
-        boolean result = false;
-        try {
-            result = c.moveToFirst();
-        } finally {
-            c.close();
-        }
-        Log.d(TAG, "shortcutWasRestored is " + result + " for " + componentName);
-        return result;
+        return !getItemsByPackageName(component.getPackageName(), user).isEmpty();
     }
 
     /**
@@ -1077,19 +1060,23 @@
                 | ((int) screen & 0xFF) << 16 | (localCellX & 0xFF) << 8 | (localCellY & 0xFF);
     }
 
-    /**
-     * Removes all the items from the database corresponding to the specified package.
-     */
-    static void deletePackageFromDatabase(Context context, final String pn,
-            final UserHandleCompat user) {
+    private static ArrayList<ItemInfo> getItemsByPackageName(
+            final String pn, final UserHandleCompat user) {
         ItemInfoFilter filter  = new ItemInfoFilter() {
             @Override
             public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
                 return cn.getPackageName().equals(pn) && info.user.equals(user);
             }
         };
-        ArrayList<ItemInfo> infos = filterItemInfos(sBgItemsIdMap.values(), filter);
-        deleteItemsFromDatabase(context, infos);
+        return filterItemInfos(sBgItemsIdMap.values(), filter);
+    }
+
+    /**
+     * Removes all the items from the database corresponding to the specified package.
+     */
+    static void deletePackageFromDatabase(Context context, final String pn,
+            final UserHandleCompat user) {
+        deleteItemsFromDatabase(context, getItemsByPackageName(pn, user));
     }
 
     /**
@@ -1898,6 +1885,7 @@
 
             synchronized (sBgLock) {
                 clearSBgDataStructures();
+                PackageInstallerCompat.getInstance(mContext).updateActiveSessionCache();
 
                 final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
                 final ArrayList<Long> restoredRows = new ArrayList<Long>();
@@ -1971,6 +1959,7 @@
                                 intentDescription = c.getString(intentIndex);
                                 long serialNumber = c.getInt(profileIdIndex);
                                 user = mUserManager.getUserForSerialNumber(serialNumber);
+                                int promiseType = c.getInt(restoredIndex);
                                 if (user == null) {
                                     // User has been deleted remove the item.
                                     itemsToRemove.add(id);
@@ -1992,12 +1981,34 @@
                                                 restored = false;
                                             }
                                         } else if (validPkg) {
-                                            // The app is installed but the component is no
-                                            // longer available.
-                                            Launcher.addDumpLog(TAG,
-                                                    "Invalid component removed: " + cn, true);
-                                            itemsToRemove.add(id);
-                                            continue;
+                                            intent = null;
+                                            if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
+                                                // We allow auto install apps to have their intent
+                                                // updated after an install.
+                                                intent = manager.getLaunchIntentForPackage(
+                                                        cn.getPackageName());
+                                                if (intent != null) {
+                                                    ContentValues values = new ContentValues();
+                                                    values.put(LauncherSettings.Favorites.INTENT,
+                                                            intent.toUri(0));
+                                                    String where = BaseColumns._ID + "= ?";
+                                                    String[] args = {Long.toString(id)};
+                                                    contentResolver.update(contentUri, values, where, args);
+                                                }
+                                            }
+
+                                            if (intent == null) {
+                                                // The app is installed but the component is no
+                                                // longer available.
+                                                Launcher.addDumpLog(TAG,
+                                                        "Invalid component removed: " + cn, true);
+                                                itemsToRemove.add(id);
+                                                continue;
+                                            } else {
+                                                // no special handling necessary for this item
+                                                restoredRows.add(id);
+                                                restored = false;
+                                            }
                                         } else if (restored) {
                                             // Package is not yet available but might be
                                             // installed later.
@@ -2036,7 +2047,7 @@
                                         Launcher.addDumpLog(TAG,
                                                 "constructing info for partially restored package",
                                                 true);
-                                        info = getRestoredItemInfo(c, titleIndex, intent);
+                                        info = getRestoredItemInfo(c, titleIndex, intent, promiseType);
                                         intent = getRestoredItemIntent(c, context, intent);
                                     } else {
                                         // Don't restore items for other profiles.
@@ -2299,7 +2310,7 @@
                         selectionBuilder.append(")");
                         ContentValues values = new ContentValues();
                         values.put(LauncherSettings.Favorites.RESTORED, 0);
-                        updater.update(LauncherSettings.Favorites.CONTENT_URI,
+                        updater.update(LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION,
                                 values, selectionBuilder.toString(), null);
                     } catch (RemoteException e) {
                         Log.w(TAG, "Could not update restored rows");
@@ -2879,7 +2890,7 @@
                                 packagesRemoved.toArray(new String[packagesRemoved.size()]), user));
                     }
                 }
-            sPendingPackages.clear();
+                sPendingPackages.clear();
             }
         }
     }
@@ -3101,21 +3112,31 @@
      * Make an ShortcutInfo object for a restored application or shortcut item that points
      * to a package that is not yet installed on the system.
      */
-    public ShortcutInfo getRestoredItemInfo(Cursor cursor, int titleIndex, Intent intent) {
+    public ShortcutInfo getRestoredItemInfo(Cursor cursor, int titleIndex, Intent intent,
+            int promiseType) {
         final ShortcutInfo info = new ShortcutInfo();
-        if (cursor != null) {
-            info.title =  cursor.getString(titleIndex);
-        } else {
-            info.title = "";
-        }
         info.user = UserHandleCompat.myUserHandle();
+        mIconCache.getTitleAndIcon(info, intent, info.user, true);
+
+        if ((promiseType & ShortcutInfo.FLAG_RESTORED_ICON) != 0) {
+            String title = (cursor != null) ? cursor.getString(titleIndex) : null;
+            if (!TextUtils.isEmpty(title)) {
+                info.title = title;
+            }
+            info.status = ShortcutInfo.FLAG_RESTORED_ICON;
+        } else if  ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
+            if (TextUtils.isEmpty(info.title)) {
+                info.title = (cursor != null) ? cursor.getString(titleIndex) : "";
+            }
+            info.status = ShortcutInfo.FLAG_AUTOINTALL_ICON;
+        } else {
+            throw new InvalidParameterException("Invalid restoreType " + promiseType);
+        }
+
         info.contentDescription = mUserManager.getBadgedLabelForUser(
                 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;
-        info.setState(ShortcutInfo.PACKAGE_STATE_UNKNOWN);
+        info.promisedIntent = intent;
         return info;
     }
 
@@ -3230,20 +3251,14 @@
         for (ItemInfo i : infos) {
             if (i instanceof ShortcutInfo) {
                 ShortcutInfo info = (ShortcutInfo) i;
-                ComponentName cn = info.intent.getComponent();
-                if (info.restoredIntent != null) {
-                    cn = info.restoredIntent.getComponent();
-                }
+                ComponentName cn = info.getTargetComponent();
                 if (cn != null && f.filterItem(null, info, cn)) {
                     filtered.add(info);
                 }
             } else if (i instanceof FolderInfo) {
                 FolderInfo info = (FolderInfo) i;
                 for (ShortcutInfo s : info.contents) {
-                    ComponentName cn = s.intent.getComponent();
-                    if (s.restoredIntent != null) {
-                        cn = s.restoredIntent.getComponent();
-                    }
+                    ComponentName cn = s.getTargetComponent();
                     if (cn != null && f.filterItem(info, s, cn)) {
                         filtered.add(s);
                     }
@@ -3287,7 +3302,7 @@
                 return true;
             }
             // placeholder shortcuts get special treatment, let them through too.
-            if (info.getRestoredIntent() != null) {
+            if (info.isPromise()) {
                 return true;
             }
         }
diff --git a/src/com/android/launcher3/PreloadIconDrawable.java b/src/com/android/launcher3/PreloadIconDrawable.java
index 1b2d5a4..2972c4f 100644
--- a/src/com/android/launcher3/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/PreloadIconDrawable.java
@@ -189,11 +189,10 @@
 
     /**
      * Runs the finish animation if it is has not been run after last level change.
-     * @return true if the animation was run.
      */
-    public boolean maybePerformFinishedAnimation() {
+    public void maybePerformFinishedAnimation() {
         if (mAnimationProgress > ANIMATION_PROGRESS_STOPPED) {
-            return false;
+            return;
         }
         if (mAnimator != null) {
             mAnimator.cancel();
@@ -202,7 +201,6 @@
         mAnimator = ObjectAnimator.ofFloat(this, "animationProgress",
                 ANIMATION_PROGRESS_STARTED, ANIMATION_PROGRESS_COMPLETED);
         mAnimator.start();
-        return true;
     }
 
     public void setAnimationProgress(float progress) {
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index 612b0a5..9abfb7f 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3;
 
+import android.content.ComponentName;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
@@ -32,17 +33,26 @@
  */
 public class ShortcutInfo extends ItemInfo {
 
-    /** {@link #mState} meaning this package is not installed, and there is no other information. */
-    public static final int PACKAGE_STATE_UNKNOWN = -2;
+    public static final int DEFAULT = 0;
 
-    /** {@link #mState} meaning this package is not installed, because installation failed. */
-    public static final int PACKAGE_STATE_ERROR = -1;
+    /**
+     * The shortcut was restored from a backup and it not ready to be used. This is automatically
+     * set during backup/restore
+     */
+    public static final int FLAG_RESTORED_ICON = 1;
 
-    /** {@link #mState} meaning this package is installed.  This is the typical case. */
-    public static final int PACKAGE_STATE_DEFAULT = 0;
+    /**
+     * The icon was added as an auto-install app, and is not ready to be used. This flag can't
+     * be present along with {@link #FLAG_RESTORED_ICON}, and is set during default layout
+     * parsing.
+     */
+    public static final int FLAG_AUTOINTALL_ICON = 2;
 
-    /** {@link #mState} meaning some external entity has promised to install this package. */
-    public static final int PACKAGE_STATE_INSTALLING = 1;
+    /**
+     * The icon is being installed. If {@link FLAG_RESTORED_ICON} or {@link FLAG_AUTOINTALL_ICON}
+     * is set, then the icon is either being installed or is in a broken state.
+     */
+    public static final int FLAG_INSTALL_SESSION_ACTIVE = 4;
 
     /**
      * The intent used to start the application.
@@ -78,29 +88,29 @@
      */
     boolean isDisabled = false;
 
-    /**
-     * The installation state of the package that this shortcut represents.
-     */
-    protected int mState;
+    int status;
 
     /**
      * The installation progress [0-100] of the package that this shortcut represents.
      */
-    protected int mProgress;
+    private int mInstallProgress;
 
+    /**
+     * Refer {@link AppInfo#firstInstallTime}.
+     */
     long firstInstallTime;
+
+    /**
+     * TODO move this to {@link status}
+     */
     int flags = 0;
 
     /**
      * If this shortcut is a placeholder, then intent will be a market intent for the package, and
      * this will hold the original intent from the database.  Otherwise, null.
+     * Refer {@link #FLAG_RESTORE_PENDING}, {@link #FLAG_INSTALL_PENDING}
      */
-    Intent restoredIntent;
-
-    /**
-     * This is set once to indicate that it was a promise info at some point of its life.
-     */
-    boolean wasPromise = false;
+    Intent promisedIntent;
 
     ShortcutInfo() {
         itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT;
@@ -110,21 +120,6 @@
         return intent;
     }
 
-    protected Intent getRestoredIntent() {
-        return restoredIntent;
-    }
-
-    /**
-     * Overwrite placeholder data with restored data, or do nothing if this is not a placeholder.
-     */
-    public void restore() {
-        if (restoredIntent != null) {
-            intent = restoredIntent;
-            restoredIntent = null;
-            mState = PACKAGE_STATE_DEFAULT;
-        }
-    }
-
     ShortcutInfo(Intent intent, CharSequence title, CharSequence contentDescription,
             Bitmap icon, UserHandleCompat user) {
         this();
@@ -149,6 +144,7 @@
         flags = info.flags;
         firstInstallTime = info.firstInstallTime;
         user = info.user;
+        status = info.status;
     }
 
     /** TODO: Remove this.  It's only called by ApplicationInfo.makeShortcut. */
@@ -184,7 +180,7 @@
         String titleStr = title != null ? title.toString() : null;
         values.put(LauncherSettings.BaseLauncherColumns.TITLE, titleStr);
 
-        String uri = restoredIntent != null ? restoredIntent.toUri(0)
+        String uri = promisedIntent != null ? promisedIntent.toUri(0)
                 : (intent != null ? intent.toUri(0) : null);
         values.put(LauncherSettings.BaseLauncherColumns.INTENT, uri);
 
@@ -224,36 +220,26 @@
         }
     }
 
-    public boolean isPromise() {
-        return restoredIntent != null;
+    public ComponentName getTargetComponent() {
+        return promisedIntent != null ? promisedIntent.getComponent() : intent.getComponent();
     }
 
-    public boolean isPromiseFor(String pkgName) {
-        return restoredIntent != null
-                && pkgName != null
-                && pkgName.equals(restoredIntent.getComponent().getPackageName());
+    public boolean hasStatusFlag(int flag) {
+        return (status & flag) != 0;
     }
 
-    public boolean isAbandoned() {
-        return isPromise()
-                && (mState == PACKAGE_STATE_ERROR
-                        || mState == PACKAGE_STATE_UNKNOWN);
+
+    public final boolean isPromise() {
+        return hasStatusFlag(FLAG_RESTORED_ICON | FLAG_AUTOINTALL_ICON);
     }
 
-    public int getProgress() {
-        return mProgress;
+    public int getInstallProgress() {
+        return mInstallProgress;
     }
 
-    public void setProgress(int progress) {
-        mProgress = progress;
-    }
-
-    public void setState(int state) {
-        mState = state;
-    }
-
-    public int getState() {
-        return mState;
+    public void setInstallProgress(int progress) {
+        mInstallProgress = progress;
+        status |= FLAG_INSTALL_SESSION_ACTIVE;
     }
 }
 
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 732b9ba..e13304a 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -31,7 +31,10 @@
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
@@ -65,6 +68,7 @@
 import com.android.launcher3.FolderIcon.FolderRingAnimator;
 import com.android.launcher3.Launcher.CustomContentCallbacks;
 import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.compat.PackageInstallerCompat;
 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
 import com.android.launcher3.compat.UserHandleCompat;
 
@@ -72,6 +76,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -4749,26 +4754,6 @@
         stripEmptyScreens();
     }
 
-    private void updateShortcut(HashMap<ComponentName, AppInfo> appsMap, ItemInfo info,
-                                View child) {
-        ComponentName cn = info.getIntent().getComponent();
-        if (info.getRestoredIntent() != null) {
-            cn = info.getRestoredIntent().getComponent();
-        }
-        if (cn != null) {
-            AppInfo appInfo = appsMap.get(cn);
-            if ((appInfo != null) && LauncherModel.isShortcutInfoUpdateable(info)) {
-                ShortcutInfo shortcutInfo = (ShortcutInfo) info;
-                BubbleTextView shortcut = (BubbleTextView) child;
-                shortcutInfo.restore();
-                shortcutInfo.updateIcon(mIconCache);
-                shortcutInfo.title = appInfo.title.toString();
-                shortcutInfo.contentDescription = appInfo.contentDescription;
-                shortcut.applyFromShortcutInfo(shortcutInfo, mIconCache, true);
-            }
-        }
-    }
-
     interface ItemOperator {
         /**
          * Process the next itemInfo, possibly with side-effect on {@link ItemOperator#value}.
@@ -4827,13 +4812,83 @@
             pkgNames.add(ai.componentName.getPackageName());
         }
 
+        final HashMap<UserHandleCompat, HashSet<ComponentName>> iconsToRemove =
+                new HashMap<UserHandleCompat, HashSet<ComponentName>>();
         mapOverItems(MAP_RECURSE, new ItemOperator() {
             @Override
             public boolean evaluate(ItemInfo info, View v, View parent) {
-                if (info instanceof ShortcutInfo) {
-                    updateShortcut(appsMap, info, v);
-                    if (parent != null) {
-                        parent.invalidate();
+                if (info instanceof ShortcutInfo && v instanceof BubbleTextView) {
+                    ShortcutInfo shortcutInfo = (ShortcutInfo) info;
+                    ComponentName cn = shortcutInfo.getTargetComponent();
+                    AppInfo appInfo = appsMap.get(cn);
+                    if (cn != null && LauncherModel.isShortcutInfoUpdateable(info)
+                            && pkgNames.contains(cn.getPackageName())) {
+                        boolean promiseStateChanged = false;
+                        boolean infoUpdated = false;
+                        if (shortcutInfo.isPromise()) {
+                            if (shortcutInfo.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
+                                // Auto install icon
+                                PackageManager pm = getContext().getPackageManager();
+                                ResolveInfo matched = pm.resolveActivity(
+                                        new Intent(Intent.ACTION_MAIN)
+                                        .setComponent(cn).addCategory(Intent.CATEGORY_LAUNCHER),
+                                        PackageManager.MATCH_DEFAULT_ONLY);
+                                if (matched == null) {
+                                    // Try to find the best match activity.
+                                    Intent intent = pm.getLaunchIntentForPackage(
+                                            cn.getPackageName());
+                                    if (intent != null) {
+                                        cn = intent.getComponent();
+                                        appInfo = appsMap.get(cn);
+                                    }
+
+                                    if ((intent == null) || (appsMap == null)) {
+                                        // Could not find a default activity. Remove this item.
+                                        HashSet<ComponentName> cnSet = iconsToRemove
+                                                .get(shortcutInfo.user);
+                                        if (cnSet == null) {
+                                            cnSet = new HashSet<>();
+                                            iconsToRemove.put(shortcutInfo.user, cnSet);
+                                        }
+                                        cnSet.add(shortcutInfo.getTargetComponent());
+
+                                        // process next shortcut.
+                                        return false;
+                                    }
+                                    shortcutInfo.promisedIntent = intent;
+                                }
+                            }
+
+                            // Restore the shortcut.
+                            shortcutInfo.intent = shortcutInfo.promisedIntent;
+                            shortcutInfo.promisedIntent = null;
+                            shortcutInfo.status &= ~ShortcutInfo.FLAG_RESTORED_ICON
+                                    & ~ShortcutInfo.FLAG_AUTOINTALL_ICON
+                                    & ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
+
+                            promiseStateChanged = true;
+                            infoUpdated = true;
+                            shortcutInfo.updateIcon(mIconCache);
+                            LauncherModel.updateItemInDatabase(getContext(), shortcutInfo);
+                        }
+
+
+                        if (appInfo != null) {
+                            shortcutInfo.updateIcon(mIconCache);
+                            shortcutInfo.title = appInfo.title.toString();
+                            shortcutInfo.contentDescription = appInfo.contentDescription;
+                            infoUpdated = true;
+                        }
+
+                        if (infoUpdated) {
+                            BubbleTextView shortcut = (BubbleTextView) v;
+                            shortcut.applyFromShortcutInfo(shortcutInfo,
+                                    mIconCache, true, promiseStateChanged);
+
+                            if (parent != null) {
+                                parent.invalidate();
+                            }
+                        }
                     }
                 }
                 // process all the shortcuts
@@ -4841,6 +4896,12 @@
             }
         });
 
+        if (!iconsToRemove.isEmpty()) {
+            for (Map.Entry<UserHandleCompat, HashSet<ComponentName>> entry :
+                iconsToRemove.entrySet()) {
+                removeItemsByComponentName(entry.getValue(), entry.getKey());
+            }
+        }
         restorePendingWidgets(pkgNames);
     }
 
@@ -4858,12 +4919,18 @@
             mapOverItems(MAP_RECURSE, new ItemOperator() {
                 @Override
                 public boolean evaluate(ItemInfo info, View v, View parent) {
-                    if (info instanceof ShortcutInfo
-                            && ((ShortcutInfo) info).isPromiseFor(installInfo.packageName)
-                            && v instanceof BubbleTextView) {
-                        ((ShortcutInfo) info).setProgress(installInfo.progress);
-                        ((ShortcutInfo) info).setState(installInfo.state);
-                        ((BubbleTextView)v).applyState();
+                    if (info instanceof ShortcutInfo && v instanceof BubbleTextView) {
+                        ShortcutInfo si = (ShortcutInfo) info;
+                        ComponentName cn = si.getTargetComponent();
+                        if (si.isPromise() && (cn != null)
+                                && installInfo.packageName.equals(cn.getPackageName())) {
+                            si.setInstallProgress(installInfo.progress);
+                            if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) {
+                                // Mark this info as broken.
+                                si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
+                            }
+                            ((BubbleTextView)v).applyState(false);
+                        }
                     } else if (v instanceof PendingAppWidgetHostView
                             && info instanceof LauncherAppWidgetInfo
                             && ((LauncherAppWidgetInfo) info).providerName.getPackageName()
@@ -4877,7 +4944,7 @@
                 }
             });
 
-            if (installInfo.state == ShortcutInfo.PACKAGE_STATE_DEFAULT) {
+            if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLED) {
                 completedPackages.add(installInfo.packageName);
             }
         }
diff --git a/src/com/android/launcher3/compat/PackageInstallerCompat.java b/src/com/android/launcher3/compat/PackageInstallerCompat.java
index 89a2157..0ae52bd 100644
--- a/src/com/android/launcher3/compat/PackageInstallerCompat.java
+++ b/src/com/android/launcher3/compat/PackageInstallerCompat.java
@@ -22,6 +22,10 @@
 
 public abstract class PackageInstallerCompat {
 
+    public static final int STATUS_INSTALLED = 0;
+    public static final int STATUS_INSTALLING = 1;
+    public static final int STATUS_FAILED = 2;
+
     private static final Object sInstanceLock = new Object();
     private static PackageInstallerCompat sInstance;
 
@@ -38,6 +42,8 @@
         }
     }
 
+    public abstract void updateActiveSessionCache();
+
     public abstract void onPause();
 
     public abstract void onResume();
diff --git a/src/com/android/launcher3/compat/PackageInstallerCompatV16.java b/src/com/android/launcher3/compat/PackageInstallerCompatV16.java
index 6a2a02e..4cc6fc1 100644
--- a/src/com/android/launcher3/compat/PackageInstallerCompatV16.java
+++ b/src/com/android/launcher3/compat/PackageInstallerCompatV16.java
@@ -22,7 +22,6 @@
 import android.util.Log;
 
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.ShortcutInfo;
 
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -77,6 +76,9 @@
     @Override
     public void onStop() { }
 
+    @Override
+    public void updateActiveSessionCache() { }
+
     private void replayUpdates() {
         if (DEBUG) Log.d(TAG, "updates resumed");
         LauncherAppState app = LauncherAppState.getInstanceNoCreate();
@@ -107,7 +109,7 @@
         PackageInstallInfo installInfo = new PackageInstallInfo(packageName);
         installInfo.progress = progress;
         installInfo.state = state;
-        if (state == ShortcutInfo.PACKAGE_STATE_DEFAULT) {
+        if (state == STATUS_INSTALLED) {
             // no longer necessary to track this package
             editor.remove(packageName);
             if (DEBUG) Log.d(TAG, "no longer tracking " + packageName);
@@ -123,7 +125,7 @@
         if (!mUseQueue) {
             if (mReplayPending) {
                 replayUpdates();
-            } else {
+            } else if (state != STATUS_INSTALLED) {
                 LauncherAppState app = LauncherAppState.getInstanceNoCreate();
                 ArrayList<PackageInstallInfo> update = new ArrayList<PackageInstallInfo>();
                 update.add(installInfo);
diff --git a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
index c78ab99..5d016a8 100644
--- a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
+++ b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
@@ -23,8 +23,8 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import com.android.launcher3.IconCache;
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.ShortcutInfo;
 
 import java.util.ArrayList;
 
@@ -35,12 +35,14 @@
 
     private final SparseArray<SessionInfo> mPendingReplays = new SparseArray<SessionInfo>();
     private final PackageInstaller mInstaller;
+    private final IconCache mCache;
 
     private boolean mResumed;
     private boolean mBound;
 
     PackageInstallerCompatVL(Context context) {
         mInstaller = context.getPackageManager().getPackageInstaller();
+        mCache = LauncherAppState.getInstance().getIconCache();
 
         mResumed = false;
         mBound = false;
@@ -53,6 +55,22 @@
     }
 
     @Override
+    public void updateActiveSessionCache() {
+        UserHandleCompat user = UserHandleCompat.myUserHandle();
+        for (SessionInfo info : mInstaller.getAllSessions()) {
+            addSessionInfoToCahce(info, user);
+        }
+    }
+
+    private void addSessionInfoToCahce(SessionInfo info, UserHandleCompat user) {
+        String packageName = info.getAppPackageName();
+        if (packageName != null) {
+            mCache.cachePackageInstallInfo(packageName, user, info.getAppIcon(),
+                    info.getAppLabel());
+        }
+    }
+
+    @Override
     public void onStop() {
         mInstaller.removeSessionCallback(mCallback);
     }
@@ -98,14 +116,14 @@
         }
 
         ArrayList<PackageInstallInfo> updates = new ArrayList<PackageInstallInfo>();
-        if (newInfo != null) {
+        if ((newInfo != null) && (newInfo.state != STATUS_INSTALLED)) {
             updates.add(newInfo);
         }
-        for (int i = mPendingReplays.size() - 1; i > 0; i--) {
+        for (int i = mPendingReplays.size() - 1; i >= 0; i--) {
             SessionInfo session = mPendingReplays.valueAt(i);
             if (session.getAppPackageName() != null) {
                 updates.add(new PackageInstallInfo(session.getAppPackageName(),
-                        ShortcutInfo.PACKAGE_STATE_INSTALLING,
+                        STATUS_INSTALLING,
                         (int) (session.getProgress() * 100)));
             }
         }
@@ -121,6 +139,7 @@
         public void onCreated(int sessionId) {
             SessionInfo session = mInstaller.getSessionInfo(sessionId);
             if (session != null) {
+                addSessionInfoToCahce(session, UserHandleCompat.myUserHandle());
                 mPendingReplays.put(sessionId, session);
                 replayUpdates(null);
             }
@@ -135,8 +154,7 @@
                 // need to store this record for future updates, as the app list will get
                 // refreshed on resume.
                 replayUpdates(new PackageInstallInfo(session.getAppPackageName(),
-                        success ? ShortcutInfo.PACKAGE_STATE_DEFAULT
-                                : ShortcutInfo.PACKAGE_STATE_ERROR, 0));
+                        success ? STATUS_INSTALLED : STATUS_FAILED, 0));
             }
         }