Merge "Initial implementation: Broadcasts on app launch." into jb-ub-gel-agar
diff --git a/src/com/android/launcher3/ApplicationInfo.java b/src/com/android/launcher3/ApplicationInfo.java
index 1bc147a..1b396f7 100644
--- a/src/com/android/launcher3/ApplicationInfo.java
+++ b/src/com/android/launcher3/ApplicationInfo.java
@@ -60,6 +60,10 @@
         itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT;
     }
 
+    protected Intent getIntent() {
+        return intent;
+    }
+
     /**
      * Must not hold the Context.
      */
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 763ec3f..c98f761 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -55,6 +55,8 @@
     private TransitionDrawable mRemoveDrawable;
     private TransitionDrawable mCurrentDrawable;
 
+    private boolean mWaitingForUninstall = false;
+
     public DeleteDropTarget(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
@@ -251,7 +253,6 @@
          return AppsCustomizePagedView.DISABLE_ALL_APPS && isWorkspaceOrFolderApplication(d);
     }
 
-    private boolean mWaitingForUninstall = false;
     private void completeDrop(final DragObject d) {
         ItemInfo item = (ItemInfo) d.dragInfo;
         boolean wasWaitingForUninstall = mWaitingForUninstall;
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index bb3993e..c70cbe0 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -775,7 +775,7 @@
         if (target != this) {
             if (mOnExitAlarm.alarmPending()) {
                 mOnExitAlarm.cancelAlarm();
-                if (successfulDrop) {
+                if (!successfulDrop) {
                     mSuppressFolderDeletion = true;
                 }
                 completeDragExit();
diff --git a/src/com/android/launcher3/FolderIcon.java b/src/com/android/launcher3/FolderIcon.java
index 92f126c..fa713a4 100644
--- a/src/com/android/launcher3/FolderIcon.java
+++ b/src/com/android/launcher3/FolderIcon.java
@@ -27,6 +27,7 @@
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.os.Looper;
 import android.os.Parcelable;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
@@ -167,8 +168,6 @@
         public float mOuterRingSize;
         public float mInnerRingSize;
         public FolderIcon mFolderIcon = null;
-        public Drawable mOuterRingDrawable = null;
-        public Drawable mInnerRingDrawable = null;
         public static Drawable sSharedOuterRingDrawable = null;
         public static Drawable sSharedInnerRingDrawable = null;
         public static int sPreviewSize = -1;
@@ -180,12 +179,14 @@
         public FolderRingAnimator(Launcher launcher, FolderIcon folderIcon) {
             mFolderIcon = folderIcon;
             Resources res = launcher.getResources();
-            mOuterRingDrawable = res.getDrawable(R.drawable.portal_ring_outer_holo);
-            mInnerRingDrawable = res.getDrawable(R.drawable.portal_ring_inner_holo);
 
             // We need to reload the static values when configuration changes in case they are
             // different in another configuration
             if (sStaticValuesDirty) {
+                if (Looper.myLooper() != Looper.getMainLooper()) {
+                    throw new RuntimeException("FolderRingAnimator loading drawables on non-UI thread "
+                            + Thread.currentThread());
+                }
                 sPreviewSize = res.getDimensionPixelSize(R.dimen.folder_preview_size);
                 sPreviewPadding = res.getDimensionPixelSize(R.dimen.folder_preview_padding);
                 sSharedOuterRingDrawable = res.getDrawable(R.drawable.portal_ring_outer_holo);
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index f97ed53..244b3db 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -38,8 +38,6 @@
 public class InstallShortcutReceiver extends BroadcastReceiver {
     public static final String ACTION_INSTALL_SHORTCUT =
             "com.android.launcher3.action.INSTALL_SHORTCUT";
-    public static final String NEW_APPS_PAGE_KEY = "apps.new.page";
-    public static final String NEW_APPS_LIST_KEY = "apps.new.list";
 
     public static final String DATA_INTENT_KEY = "intent.data";
     public static final String LAUNCH_INTENT_KEY = "intent.launch";
@@ -51,11 +49,10 @@
     public static final String APPS_PENDING_INSTALL = "apps_to_install";
 
     public static final int NEW_SHORTCUT_BOUNCE_DURATION = 450;
-    public static final int NEW_SHORTCUT_STAGGER_DELAY = 75;
+    public static final int NEW_SHORTCUT_STAGGER_DELAY = 85;
 
     private static final int INSTALL_SHORTCUT_SUCCESSFUL = 0;
     private static final int INSTALL_SHORTCUT_IS_DUPLICATE = -1;
-    private static final int INSTALL_SHORTCUT_NO_SPACE = -2;
 
     // A mime-type representing shortcut data
     public static final String SHORTCUT_MIMETYPE =
@@ -201,12 +198,12 @@
         PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(data, name, intent);
         info.icon = icon;
         info.iconResource = iconResource;
-        if (mUseInstallQueue || launcherNotLoaded) {
-            String spKey = LauncherAppState.getSharedPreferencesKey();
-            SharedPreferences sp = context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
-            addToInstallQueue(sp, info);
-        } else {
-            processInstallShortcut(context, info);
+
+        String spKey = LauncherAppState.getSharedPreferencesKey();
+        SharedPreferences sp = context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
+        addToInstallQueue(sp, info);
+        if (!mUseInstallQueue && !launcherNotLoaded) {
+            flushInstallQueue(context);
         }
     }
 
@@ -221,142 +218,59 @@
         String spKey = LauncherAppState.getSharedPreferencesKey();
         SharedPreferences sp = context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
         ArrayList<PendingInstallShortcutInfo> installQueue = getAndClearInstallQueue(sp);
-        Iterator<PendingInstallShortcutInfo> iter = installQueue.iterator();
-        while (iter.hasNext()) {
-            processInstallShortcut(context, iter.next());
+        if (!installQueue.isEmpty()) {
+            Iterator<PendingInstallShortcutInfo> iter = installQueue.iterator();
+            ArrayList<ItemInfo> addShortcuts = new ArrayList<ItemInfo>();
+            int result = INSTALL_SHORTCUT_SUCCESSFUL;
+            String duplicateName = "";
+            while (iter.hasNext()) {
+                final PendingInstallShortcutInfo pendingInfo = iter.next();
+                final Intent data = pendingInfo.data;
+                final Intent intent = pendingInfo.launchIntent;
+                final String name = pendingInfo.name;
+                final boolean exists = LauncherModel.shortcutExists(context, name, intent);
+                final boolean allowDuplicate = data.getBooleanExtra(Launcher.EXTRA_SHORTCUT_DUPLICATE, true);
+
+                // TODO-XXX: Disable duplicates for now
+                if (!exists /* && allowDuplicate */) {
+                    // Generate a shortcut info to add into the model
+                    ShortcutInfo info = getShortcutInfo(context, pendingInfo.data,
+                            pendingInfo.launchIntent);
+                    addShortcuts.add(info);
+                }
+                /*
+                else if (exists && !allowDuplicate) {
+                    result = INSTALL_SHORTCUT_IS_DUPLICATE;
+                    duplicateName = name;
+                }
+                */
+            }
+
+            // Notify the user once if we weren't able to place any duplicates
+            if (result == INSTALL_SHORTCUT_IS_DUPLICATE) {
+                Toast.makeText(context, context.getString(R.string.shortcut_duplicate,
+                        duplicateName), Toast.LENGTH_SHORT).show();
+            }
+
+            // Add the new apps to the model and bind them
+            if (!addShortcuts.isEmpty()) {
+                LauncherAppState app = LauncherAppState.getInstance();
+                app.getModel().addAndBindAddedApps(context, addShortcuts);
+            }
         }
     }
 
-    private static void processInstallShortcut(Context context,
-            PendingInstallShortcutInfo pendingInfo) {
-        String spKey = LauncherAppState.getSharedPreferencesKey();
-        SharedPreferences sp = context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
-
-        final Intent data = pendingInfo.data;
-        final Intent intent = pendingInfo.launchIntent;
-        final String name = pendingInfo.name;
-
-        // Lock on the app so that we don't try and get the items while apps are being added
+    private static ShortcutInfo getShortcutInfo(Context context, Intent data,
+                                                Intent launchIntent) {
+        if (launchIntent.getAction() == null) {
+            launchIntent.setAction(Intent.ACTION_VIEW);
+        } else if (launchIntent.getAction().equals(Intent.ACTION_MAIN) &&
+                launchIntent.getCategories() != null &&
+                launchIntent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
+            launchIntent.addFlags(
+                    Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+        }
         LauncherAppState app = LauncherAppState.getInstance();
-        final int[] result = {INSTALL_SHORTCUT_SUCCESSFUL};
-        boolean found = false;
-        synchronized (app) {
-            // Flush the LauncherModel worker thread, so that if we just did another
-            // processInstallShortcut, we give it time for its shortcut to get added to the
-            // database (getItemsInLocalCoordinates reads the database)
-            app.getModel().flushWorkerThread();
-            final ArrayList<ItemInfo> items = LauncherModel.getItemsInLocalCoordinates(context);
-            final boolean exists = LauncherModel.shortcutExists(context, name, intent);
-
-            // Try adding to the workspace screens incrementally, starting at the default or center
-            // screen and alternating between +1, -1, +2, -2, etc. (using ~ ceil(i/2f)*(-1)^(i-1))
-            final int screen = Launcher.DEFAULT_SCREEN;
-            for (int i = 0; i < Launcher.SCREEN_COUNT && !found; i++) {
-                int si = i;
-                if (0 <= si && si < Launcher.SCREEN_COUNT) {
-                    found = installShortcut(context, data, items, name, intent, si, exists, sp,
-                            result);
-                }
-            }
-        }
-
-        // We only report error messages (duplicate shortcut or out of space) as the add-animation
-        // will provide feedback otherwise
-        if (!found) {
-            if (result[0] == INSTALL_SHORTCUT_NO_SPACE) {
-                Toast.makeText(context, context.getString(R.string.completely_out_of_space),
-                        Toast.LENGTH_SHORT).show();
-            } else if (result[0] == INSTALL_SHORTCUT_IS_DUPLICATE) {
-                Toast.makeText(context, context.getString(R.string.shortcut_duplicate, name),
-                        Toast.LENGTH_SHORT).show();
-            }
-        }
-    }
-
-    private static boolean installShortcut(Context context, Intent data, ArrayList<ItemInfo> items,
-            String name, final Intent intent, final int screen, boolean shortcutExists,
-            final SharedPreferences sharedPrefs, int[] result) {
-        int[] tmpCoordinates = new int[2];
-        if (findEmptyCell(context, items, tmpCoordinates, screen)) {
-            if (intent != null) {
-                if (intent.getAction() == null) {
-                    intent.setAction(Intent.ACTION_VIEW);
-                } else if (intent.getAction().equals(Intent.ACTION_MAIN) &&
-                        intent.getCategories() != null &&
-                        intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
-                    intent.addFlags(
-                        Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
-                }
-
-                // By default, we allow for duplicate entries (located in
-                // different places)
-                boolean duplicate = data.getBooleanExtra(Launcher.EXTRA_SHORTCUT_DUPLICATE, true);
-                if (duplicate || !shortcutExists) {
-                    new Thread("setNewAppsThread") {
-                        public void run() {
-                            synchronized (sLock) {
-                                // If the new app is going to fall into the same page as before,
-                                // then just continue adding to the current page
-                                final int newAppsScreen = sharedPrefs.getInt(
-                                        NEW_APPS_PAGE_KEY, screen);
-                                SharedPreferences.Editor editor = sharedPrefs.edit();
-                                if (newAppsScreen == -1 || newAppsScreen == screen) {
-                                    addToStringSet(sharedPrefs,
-                                        editor, NEW_APPS_LIST_KEY, intent.toUri(0));
-                                }
-                                editor.putInt(NEW_APPS_PAGE_KEY, screen);
-                                editor.commit();
-                            }
-                        }
-                    }.start();
-
-                    // Update the Launcher db
-                    LauncherAppState app = LauncherAppState.getInstance();
-                    ShortcutInfo info = app.getModel().addShortcut(context, data,
-                            LauncherSettings.Favorites.CONTAINER_DESKTOP, screen,
-                            tmpCoordinates[0], tmpCoordinates[1], true);
-                    if (info == null) {
-                        return false;
-                    }
-                } else {
-                    result[0] = INSTALL_SHORTCUT_IS_DUPLICATE;
-                }
-
-                return true;
-            }
-        } else {
-            result[0] = INSTALL_SHORTCUT_NO_SPACE;
-        }
-
-        return false;
-    }
-
-    // TODO: this needs to be updated to take a screenId instead of a screen index
-    private static boolean findEmptyCell(Context context, ArrayList<ItemInfo> items, int[] xy,
-            int screen) {
-        final int xCount = LauncherModel.getCellCountX();
-        final int yCount = LauncherModel.getCellCountY();
-        boolean[][] occupied = new boolean[xCount][yCount];
-
-        ItemInfo item = null;
-        int cellX, cellY, spanX, spanY;
-        for (int i = 0; i < items.size(); ++i) {
-            item = items.get(i);
-            if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
-                if (item.screenId == screen) {
-                    cellX = item.cellX;
-                    cellY = item.cellY;
-                    spanX = item.spanX;
-                    spanY = item.spanY;
-                    for (int x = cellX; 0 <= x && x < cellX + spanX && x < xCount; x++) {
-                        for (int y = cellY; 0 <= y && y < cellY + spanY && y < yCount; y++) {
-                            occupied[x][y] = true;
-                        }
-                    }
-                }
-            }
-        }
-
-        return CellLayout.findVacantCell(xy, 1, 1, xCount, yCount, occupied);
+        return app.getModel().infoFromShortcutIntent(context, data, null);
     }
 }
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index 7ab30a9..8c4cefd 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -118,6 +118,10 @@
         LauncherModel.checkItemInfo(this);
     }
 
+    protected Intent getIntent() {
+        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 c3ea1ef..bd343ea 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -311,8 +311,6 @@
 
     // Holds the page that we need to animate to, and the icon views that we need to animate up
     // when we scroll to that page on resume.
-    private long mNewShortcutAnimateScreenId = -1;
-    private ArrayList<View> mNewShortcutAnimateViews = new ArrayList<View>();
     private ImageView mFolderIconImageView;
     private Bitmap mFolderIconBitmap;
     private Canvas mFolderIconCanvas;
@@ -773,7 +771,7 @@
         setWorkspaceBackground(mState == State.WORKSPACE);
 
         // Process any items that were added while Launcher was away
-        InstallShortcutReceiver.flushInstallQueue(this);
+        InstallShortcutReceiver.disableAndFlushInstallQueue(this);
 
         mPaused = false;
         sPausedFromUserAction = false;
@@ -806,6 +804,12 @@
                     (System.currentTimeMillis() - startTimeCallbacks));
             }
         }
+        if (mOnResumeCallbacks.size() > 0) {
+            for (int i = 0; i < mOnResumeCallbacks.size(); i++) {
+                mOnResumeCallbacks.get(i).run();
+            }
+            mOnResumeCallbacks.clear();
+        }
 
         // Reset the pressed state of icons that were locked in the press state while activities
         // were launching
@@ -838,6 +842,9 @@
         // when Launcher resumes and we are still in AllApps.
         updateWallpaperVisibility(true);
 
+        // Ensure that items added to Launcher are queued until Launcher returns
+        InstallShortcutReceiver.enableInstallQueue();
+
         super.onPause();
         mPaused = true;
         mDragController.cancelDrag();
@@ -3445,11 +3452,11 @@
     }
 
     public void addOnResumeCallback(Runnable run) {
-        mBindOnResumeCallbacks.add(run);
+        mOnResumeCallbacks.add(run);
     }
 
     public void removeOnResumeCallback(Runnable run) {
-        mBindOnResumeCallbacks.remove(run);
+        mOnResumeCallbacks.remove(run);
     }
 
     /**
@@ -3499,8 +3506,6 @@
         mBindOnResumeCallbacks.clear();
 
         final Workspace workspace = mWorkspace;
-        mNewShortcutAnimateScreenId = -1;
-        mNewShortcutAnimateViews.clear();
         mWorkspace.clearDropTargets();
         int count = workspace.getChildCount();
         for (int i = 0; i < count; i++) {
@@ -3568,12 +3573,11 @@
         }
 
         // Get the list of added shortcuts and intersect them with the set of shortcuts here
-        Set<String> newApps = new HashSet<String>();
-        newApps = mSharedPrefs.getStringSet(InstallShortcutReceiver.NEW_APPS_LIST_KEY, newApps);
-
         final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
         final Collection<Animator> bounceAnims = new ArrayList<Animator>();
+        final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation();
         Workspace workspace = mWorkspace;
+        long newShortcutsScreenId = -1;
         for (int i = start; i < end; i++) {
             final ItemInfo item = shortcuts.get(i);
 
@@ -3587,7 +3591,6 @@
                 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                     ShortcutInfo info = (ShortcutInfo) item;
-                    String uri = info.intent.toUri(0).toString();
                     View shortcut = createShortcut(info);
 
                     /*
@@ -3602,27 +3605,13 @@
 
                     workspace.addInScreenFromBind(shortcut, item.container, item.screenId, item.cellX,
                             item.cellY, 1, 1);
-                    boolean animateIconUp = false;
-                    synchronized (newApps) {
-                        if (newApps.contains(uri)) {
-                            animateIconUp = newApps.remove(uri);
-                        }
-                    }
-                    if (forceAnimateIcons) {
+                    if (animateIcons) {
                         // Animate all the applications up now
                         shortcut.setAlpha(0f);
                         shortcut.setScaleX(0f);
                         shortcut.setScaleY(0f);
                         bounceAnims.add(createNewAppBounceAnimation(shortcut, i));
-                    } else if (animateIconUp) {
-                        // Prepare the view to be animated up
-                        shortcut.setAlpha(0f);
-                        shortcut.setScaleX(0f);
-                        shortcut.setScaleY(0f);
-                        mNewShortcutAnimateScreenId = item.screenId;
-                        if (!mNewShortcutAnimateViews.contains(shortcut)) {
-                            mNewShortcutAnimateViews.add(shortcut);
-                        }
+                        newShortcutsScreenId = item.screenId;
                     }
                     break;
                 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
@@ -3637,7 +3626,16 @@
             }
         }
 
-        if (forceAnimateIcons) {
+        if (animateIcons) {
+            // Animate to the correct page
+            if (newShortcutsScreenId > -1) {
+                long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
+                int newScreenIndex = mWorkspace.getPageIndexForScreenId(newShortcutsScreenId);
+                if (newShortcutsScreenId != currentScreenId) {
+                    mWorkspace.snapToPage(newScreenIndex);
+                }
+            }
+
             // We post the animation slightly delayed to prevent slowdowns when we are loading
             // right after we return to launcher.
             mWorkspace.postDelayed(new Runnable() {
@@ -3750,32 +3748,6 @@
         // package changes in bindSearchablesChanged()
         updateAppMarketIcon();
 
-        // Animate up any icons as necessary
-        if (mVisible || mWorkspaceLoading) {
-            Runnable newAppsRunnable = new Runnable() {
-                @Override
-                public void run() {
-                    runNewAppsAnimation(false);
-                }
-            };
-
-            boolean willSnapPage = mNewShortcutAnimateScreenId > -1 &&
-                    mNewShortcutAnimateScreenId != mWorkspace.getCurrentPage();
-            if (canRunNewAppsAnimation()) {
-                // If the user has not interacted recently, then either snap to the new page to show
-                // the new-apps animation or just run them if they are to appear on the current page
-                if (willSnapPage) {
-                    mWorkspace.snapToScreenId(mNewShortcutAnimateScreenId, newAppsRunnable);
-                } else {
-                    runNewAppsAnimation(false);
-                }
-            } else {
-                // If the user has interacted recently, then just add the items in place if they
-                // are on another page (or just normally if they are added to the current page)
-                runNewAppsAnimation(willSnapPage);
-            }
-        }
-
         mWorkspaceLoading = false;
         if (upgradePath) {
             mWorkspace.stripDuplicateApps();
@@ -3806,64 +3778,6 @@
         return bounceAnim;
     }
 
-    /**
-     * Runs a new animation that scales up icons that were added while Launcher was in the
-     * background.
-     *
-     * @param immediate whether to run the animation or show the results immediately
-     */
-    private void runNewAppsAnimation(boolean immediate) {
-        AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
-        Collection<Animator> bounceAnims = new ArrayList<Animator>();
-
-        // Order these new views spatially so that they animate in order
-        Collections.sort(mNewShortcutAnimateViews, new Comparator<View>() {
-            @Override
-            public int compare(View a, View b) {
-                CellLayout.LayoutParams alp = (CellLayout.LayoutParams) a.getLayoutParams();
-                CellLayout.LayoutParams blp = (CellLayout.LayoutParams) b.getLayoutParams();
-                int cellCountX = LauncherModel.getCellCountX();
-                return (alp.cellY * cellCountX + alp.cellX) - (blp.cellY * cellCountX + blp.cellX);
-            }
-        });
-
-        // Animate each of the views in place (or show them immediately if requested)
-        if (immediate) {
-            for (View v : mNewShortcutAnimateViews) {
-                v.setAlpha(1f);
-                v.setScaleX(1f);
-                v.setScaleY(1f);
-            }
-        } else {
-            for (int i = 0; i < mNewShortcutAnimateViews.size(); ++i) {
-                View v = mNewShortcutAnimateViews.get(i);
-                bounceAnims.add(createNewAppBounceAnimation(v, i));
-            }
-            anim.playTogether(bounceAnims);
-            anim.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    if (mWorkspace != null) {
-                        mWorkspace.postDelayed(mBuildLayersRunnable, 500);
-                    }
-                }
-            });
-            anim.start();
-        }
-
-        // Clean up
-        mNewShortcutAnimateScreenId = -1;
-        mNewShortcutAnimateViews.clear();
-        new Thread("clearNewAppsThread") {
-            public void run() {
-                mSharedPrefs.edit()
-                            .putInt(InstallShortcutReceiver.NEW_APPS_PAGE_KEY, -1)
-                            .putStringSet(InstallShortcutReceiver.NEW_APPS_LIST_KEY, null)
-                            .commit();
-            }
-        }.start();
-    }
-
     @Override
     public void bindSearchablesChanged() {
         boolean searchVisible = updateGlobalSearchIcon();
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index b231e3a..cec446f 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -68,7 +68,7 @@
     static final String TAG = "Launcher.Model";
 
     private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
-    private final boolean mAppsCanBeOnExternalStorage;
+    private final boolean mAppsCanBeOnRemoveableStorage;
 
     private final LauncherAppState mApp;
     private final Object mLock = new Object();
@@ -172,7 +172,7 @@
     LauncherModel(LauncherAppState app, IconCache iconCache) {
         final Context context = app.getContext();
 
-        mAppsCanBeOnExternalStorage = !Environment.isExternalStorageEmulated();
+        mAppsCanBeOnRemoveableStorage = Environment.isExternalStorageRemovable();
         mApp = app;
         mBgAllAppsList = new AllAppsList(iconCache);
         mIconCache = iconCache;
@@ -268,8 +268,15 @@
         return null;
     }
 
-    public void addAndBindAddedApps(final Context context, final ArrayList<ApplicationInfo> added,
+    public void addAndBindAddedApps(final Context context, final ArrayList<ItemInfo> added) {
+        Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
+        addAndBindAddedApps(context, added, cb);
+    }
+    public void addAndBindAddedApps(final Context context, final ArrayList<ItemInfo> added,
                                     final Callbacks callbacks) {
+        if (added.isEmpty()) {
+            throw new RuntimeException("EMPTY ADDED ARRAY?");
+        }
         // Process the newly added applications and add them to the database first
         Runnable r = new Runnable() {
             public void run() {
@@ -277,11 +284,11 @@
                 final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
 
                 synchronized(sBgLock) {
-                    Iterator<ApplicationInfo> iter = added.iterator();
+                    Iterator<ItemInfo> iter = added.iterator();
                     while (iter.hasNext()) {
-                        ApplicationInfo a = iter.next();
+                        ItemInfo a = iter.next();
                         final String name = a.title.toString();
-                        final Intent launchIntent = a.intent;
+                        final Intent launchIntent = a.getIntent();
 
                         // Short-circuit this logic if the icon exists somewhere on the workspace
                         if (LauncherModel.shortcutExists(context, name, launchIntent)) {
@@ -319,7 +326,14 @@
                             throw new RuntimeException("Coordinates should not be null");
                         }
 
-                        final ShortcutInfo shortcutInfo = a.makeShortcut();
+                        ShortcutInfo shortcutInfo;
+                        if (a instanceof ShortcutInfo) {
+                            shortcutInfo = (ShortcutInfo) a;
+                        } else if (a instanceof ApplicationInfo) {
+                            shortcutInfo = ((ApplicationInfo) a).makeShortcut();
+                        } else {
+                            throw new RuntimeException("Unexpected info type");
+                        }
                         // Add the shortcut to the db
                         addItemToDatabase(context, shortcutInfo,
                                 LauncherSettings.Favorites.CONTAINER_DESKTOP,
@@ -329,16 +343,38 @@
                     }
                 }
 
-                runOnMainThread(new Runnable() {
-                    public void run() {
-                        Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
-                        if (callbacks == cb && cb != null) {
-                            callbacks.bindAddScreens(addedWorkspaceScreensFinal);
-                            callbacks.bindItems(addedShortcutsFinal, 0,
-                                    addedShortcutsFinal.size(), true);
+                if (!addedShortcutsFinal.isEmpty()) {
+                    runOnMainThread(new Runnable() {
+                        public void run() {
+                            Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
+                            if (callbacks == cb && cb != null) {
+                                callbacks.bindAddScreens(addedWorkspaceScreensFinal);
+
+                                ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 1);
+                                long lastScreenId = info.screenId;
+                                final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
+                                final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
+                                for (ItemInfo i : addedShortcutsFinal) {
+                                    if (i.screenId == lastScreenId) {
+                                        addAnimated.add(i);
+                                    } else {
+                                        addNotAnimated.add(i);
+                                    }
+                                }
+                                // We add the items without animation on non-visible pages, and with
+                                // animations on the new page (which we will try and snap to).
+                                if (!addNotAnimated.isEmpty()) {
+                                    callbacks.bindItems(addNotAnimated, 0,
+                                            addNotAnimated.size(), false);
+                                }
+                                if (!addAnimated.isEmpty()) {
+                                    callbacks.bindItems(addAnimated, 0,
+                                            addAnimated.size(), true);
+                                }
+                            }
                         }
-                    }
-                });
+                    });
+                }
             }
         };
         runOnWorkerThread(r);
@@ -1455,7 +1491,7 @@
 
             // Cross reference all the applications in our apps list with items in the workspace
             ArrayList<ItemInfo> tmpInfos;
-            ArrayList<ApplicationInfo> added = new ArrayList<ApplicationInfo>();
+            ArrayList<ItemInfo> added = new ArrayList<ItemInfo>();
             synchronized (sBgLock) {
                 for (ApplicationInfo app : mBgAllAppsList.data) {
                     tmpInfos = getItemInfoForComponentName(app.componentName);
@@ -1606,9 +1642,24 @@
                             switch (itemType) {
                             case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                                id = c.getLong(idIndex);
                                 intentDescription = c.getString(intentIndex);
                                 try {
                                     intent = Intent.parseUri(intentDescription, 0);
+                                    ComponentName cn = intent.getComponent();
+                                    if (!isValidPackage(manager, cn)) {
+                                        if (!mAppsCanBeOnRemoveableStorage) {
+                                            // Log the invalid package, and remove it from the database
+                                            Uri uri = LauncherSettings.Favorites.getContentUri(id, false);
+                                            contentResolver.delete(uri, null, null);
+                                            Log.e(TAG, "Invalid package removed in loadWorkspace: " + cn);
+                                        } else {
+                                            // If apps can be on external storage, then we just leave
+                                            // them for the user to remove (maybe add treatment to it)
+                                            Log.e(TAG, "Invalid package found in loadWorkspace: " + cn);
+                                        }
+                                        continue;
+                                    }
                                 } catch (URISyntaxException e) {
                                     continue;
                                 }
@@ -1635,8 +1686,8 @@
                                 }
 
                                 if (info != null) {
+                                    info.id = id;
                                     info.intent = intent;
-                                    info.id = c.getLong(idIndex);
                                     container = c.getInt(containerIndex);
                                     info.container = container;
                                     info.screenId = c.getInt(screenIndex);
@@ -1664,15 +1715,6 @@
                                     // now that we've loaded everthing re-save it with the
                                     // icon in case it disappears somehow.
                                     queueIconToBeChecked(sBgDbIconCache, info, c, iconIndex);
-                                } else {
-                                    // Failed to load the shortcut, probably because the
-                                    // activity manager couldn't resolve it (maybe the app
-                                    // was uninstalled), or the db row was somehow screwed up.
-                                    // Delete it.
-                                    id = c.getLong(idIndex);
-                                    Log.e(TAG, "Error loading shortcut " + id + ", removing it");
-                                    contentResolver.delete(LauncherSettings.Favorites.getContentUri(
-                                                id, false), null, null);
                                 }
                                 break;
 
@@ -2402,8 +2444,9 @@
 
             if (added != null) {
                 // Ensure that we add all the workspace applications to the db
+                final ArrayList<ItemInfo> addedInfos = new ArrayList<ItemInfo>(added);
                 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
-                addAndBindAddedApps(context, added, cb);
+                addAndBindAddedApps(context, addedInfos, cb);
             }
             if (modified != null) {
                 final ArrayList<ApplicationInfo> modifiedFinal = modified;
@@ -2492,6 +2535,18 @@
         return widgetsAndShortcuts;
     }
 
+    private boolean isValidPackage(PackageManager pm, ComponentName cn) {
+        if (cn == null) {
+            return false;
+        }
+
+        try {
+            return (pm.getActivityInfo(cn, 0) != null);
+        } catch (NameNotFoundException e) {
+            return false;
+        }
+    }
+
     /**
      * This is called from the code that adds shortcuts from the intent receiver.  This
      * doesn't have a Cursor, but
@@ -2507,26 +2562,12 @@
      */
     public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context,
             Cursor c, int iconIndex, int titleIndex, HashMap<Object, CharSequence> labelCache) {
-        Bitmap icon = null;
-        final ShortcutInfo info = new ShortcutInfo();
-
         ComponentName componentName = intent.getComponent();
-        if (componentName == null) {
+        if (!isValidPackage(manager, componentName)) {
+            Log.d(TAG, "Invalid package found in getShortcutInfo: " + componentName);
             return null;
         }
 
-        try {
-            PackageInfo pi = manager.getPackageInfo(componentName.getPackageName(), 0);
-            if (!pi.applicationInfo.enabled) {
-                // If we return null here, the corresponding item will be removed from the launcher
-                // db and will not appear in the workspace.
-                return null;
-            }
-            info.initFlagsAndFirstInstallTime(pi);
-        } catch (NameNotFoundException e) {
-            Log.d(TAG, "getPackInfo failed for package " + componentName.getPackageName());
-        }
-
         // TODO: See if the PackageManager knows about this case.  If it doesn't
         // then return null & delete this.
 
@@ -2538,6 +2579,8 @@
         // Attempt to use queryIntentActivities to get the ResolveInfo (with IntentFilter info) and
         // if that fails, or is ambiguious, fallback to the standard way of getting the resolve info
         // via resolveActivity().
+        final ShortcutInfo info = new ShortcutInfo();
+        Bitmap icon = null;
         ResolveInfo resolveInfo = null;
         ComponentName oldComponent = intent.getComponent();
         Intent newIntent = new Intent(intent.getAction(), null);
@@ -2864,7 +2907,7 @@
     boolean queueIconToBeChecked(HashMap<Object, byte[]> cache, ShortcutInfo info, Cursor c,
             int iconIndex) {
         // If apps can't be on SD, don't even bother.
-        if (!mAppsCanBeOnExternalStorage) {
+        if (!mAppsCanBeOnRemoveableStorage) {
             return false;
         }
         // If this icon doesn't have a custom icon, check to see
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index b6139b7..b4e5642 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -67,6 +67,10 @@
     ShortcutInfo() {
         itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT;
     }
+
+    protected Intent getIntent() {
+        return intent;
+    }
     
     public ShortcutInfo(Context context, ShortcutInfo info) {
         super(info);
@@ -79,7 +83,8 @@
         }
         mIcon = info.mIcon; // TODO: should make a copy here.  maybe we don't need this ctor at all
         customIcon = info.customIcon;
-        initFlagsAndFirstInstallTime(getPackageInfo(context, intent.getComponent().getPackageName()));
+        initFlagsAndFirstInstallTime(
+                getPackageInfo(context, intent.getComponent().getPackageName()));
     }
 
     /** TODO: Remove this.  It's only called by ApplicationInfo.makeShortcut. */
@@ -137,7 +142,8 @@
         intent.setComponent(className);
         intent.setFlags(launchFlags);
         itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION;
-        initFlagsAndFirstInstallTime(getPackageInfo(context, intent.getComponent().getPackageName()));
+        initFlagsAndFirstInstallTime(
+                getPackageInfo(context, intent.getComponent().getPackageName()));
     }
 
     @Override
diff --git a/src/com/android/launcher3/UninstallShortcutReceiver.java b/src/com/android/launcher3/UninstallShortcutReceiver.java
index 8e968cd..2e1ed69 100644
--- a/src/com/android/launcher3/UninstallShortcutReceiver.java
+++ b/src/com/android/launcher3/UninstallShortcutReceiver.java
@@ -79,19 +79,15 @@
 
     private static void processUninstallShortcut(Context context,
             PendingUninstallShortcutInfo pendingInfo) {
-        String spKey = LauncherAppState.getSharedPreferencesKey();
-        SharedPreferences sharedPrefs = context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
-
         final Intent data = pendingInfo.data;
 
         LauncherAppState app = LauncherAppState.getInstance();
         synchronized (app) { // TODO: make removeShortcut internally threadsafe
-            removeShortcut(context, data, sharedPrefs);
+            removeShortcut(context, data);
         }
     }
 
-    private static void removeShortcut(Context context, Intent data,
-            final SharedPreferences sharedPrefs) {
+    private static void removeShortcut(Context context, Intent data) {
         Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
         String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
         boolean duplicate = data.getBooleanExtra(Launcher.EXTRA_SHORTCUT_DUPLICATE, true);
@@ -132,33 +128,6 @@
                 Toast.makeText(context, context.getString(R.string.shortcut_uninstalled, name),
                         Toast.LENGTH_SHORT).show();
             }
-
-            // Remove any items due to be animated
-            boolean appRemoved;
-            Set<String> newApps = new HashSet<String>();
-            newApps = sharedPrefs.getStringSet(InstallShortcutReceiver.NEW_APPS_LIST_KEY, newApps);
-            synchronized (newApps) {
-                do {
-                    appRemoved = newApps.remove(intent.toUri(0).toString());
-                } while (appRemoved);
-            }
-            if (appRemoved) {
-                final Set<String> savedNewApps = newApps;
-                new Thread("setNewAppsThread-remove") {
-                    public void run() {
-                        synchronized (savedNewApps) {
-                            SharedPreferences.Editor editor = sharedPrefs.edit();
-                            editor.putStringSet(InstallShortcutReceiver.NEW_APPS_LIST_KEY,
-                                    savedNewApps);
-                            if (savedNewApps.isEmpty()) {
-                                // Reset the page index if there are no more items
-                                editor.putInt(InstallShortcutReceiver.NEW_APPS_PAGE_KEY, -1);
-                            }
-                            editor.commit();
-                        }
-                    }
-                }.start();
-            }
         }
     }
 }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 9424eef..6989c9a 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -3969,34 +3969,6 @@
 
         // Strip all the empty screens
         stripEmptyScreens();
-
-        // Clean up new-apps animation list
-        final Context context = getContext();
-        post(new Runnable() {
-            @Override
-            public void run() {
-                String spKey = LauncherAppState.getSharedPreferencesKey();
-                SharedPreferences sp = context.getSharedPreferences(spKey,
-                        Context.MODE_PRIVATE);
-                Set<String> newApps = sp.getStringSet(InstallShortcutReceiver.NEW_APPS_LIST_KEY,
-                        null);
-
-                // Remove all queued items that match the same package
-                if (newApps != null) {
-                    synchronized (newApps) {
-                        Iterator<String> iter = newApps.iterator();
-                        while (iter.hasNext()) {
-                            try {
-                                Intent intent = Intent.parseUri(iter.next(), 0);
-                                if (componentNames.contains(intent.getComponent())) {
-                                    iter.remove();
-                                }
-                            } catch (URISyntaxException e) {}
-                        }
-                    }
-                }
-            }
-        });
     }
 
     void updateShortcuts(ArrayList<ApplicationInfo> apps) {