Merge "Fixing label cache issue due to different ResolveInfos being returned in different parts of loading, saves 1-10% on AllApps load. (5042022)"
diff --git a/res/layout/apps_customize_pane.xml b/res/layout/apps_customize_pane.xml
index 05e7fc1..95b7dc2 100644
--- a/res/layout/apps_customize_pane.xml
+++ b/res/layout/apps_customize_pane.xml
@@ -63,6 +63,11 @@
                 launcher:widgetCountX="@integer/apps_customize_widget_cell_count_x"
                 launcher:widgetCountY="@integer/apps_customize_widget_cell_count_y"
                 launcher:maxGap="@dimen/workspace_max_gap" />
+            <ImageView
+                android:id="@+id/animation_buffer"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:visibility="gone" />
 
             <include
                 android:id="@+id/paged_view_indicator"
diff --git a/res/values/config.xml b/res/values/config.xml
index 7eebc4a..37710b6 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -31,7 +31,7 @@
     <integer name="config_appsCustomizeWorkspaceShrinkTime">1000</integer>
 
     <!-- Tab transition animation duration -->
-    <integer name="config_tabTransitionDuration">100</integer>
+    <integer name="config_tabTransitionDuration">200</integer>
 
     <!-- The slope, in percent, of the drag movement needed to drag an item out of
          AppsCustomize (y / x * 100%)  -->
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 3934237..a516424 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -83,10 +83,10 @@
     <dimen name="dragViewOffsetY">-8dp</dimen>
 
     <!-- Padding applied to AppWidgets -->
-    <dimen name="app_widget_padding_left">0dp</dimen>
-    <dimen name="app_widget_padding_right">0dp</dimen>
-    <dimen name="app_widget_padding_top">0dp</dimen>
-    <dimen name="app_widget_padding_bottom">0dp</dimen>
+    <dimen name="app_widget_padding_left">3dp</dimen>
+    <dimen name="app_widget_padding_right">3dp</dimen>
+    <dimen name="app_widget_padding_top">1dp</dimen>
+    <dimen name="app_widget_padding_bottom">1dp</dimen>
 
 <!-- Folders -->
     <!-- The size of the image which sits behind the preview of the folder contents -->
diff --git a/src/com/android/launcher2/AppsCustomizePagedView.java b/src/com/android/launcher2/AppsCustomizePagedView.java
index 9d03995..949d872 100644
--- a/src/com/android/launcher2/AppsCustomizePagedView.java
+++ b/src/com/android/launcher2/AppsCustomizePagedView.java
@@ -164,6 +164,10 @@
     private final LayoutInflater mLayoutInflater;
     private final PackageManager mPackageManager;
 
+    // Save and Restore
+    private int mSaveInstanceStateItemIndex = -1;
+    private int mRestorePage = -1;
+
     // Content
     private ContentType mContentType;
     private ArrayList<ApplicationInfo> mApps;
@@ -188,6 +192,7 @@
     // Previews & outlines
     ArrayList<AppsCustomizeAsyncTask> mRunningTasks;
     private HolographicOutlineHelper mHolographicOutlineHelper;
+    private static final int sPageSleepDelay = 200;
 
     public AppsCustomizePagedView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -252,6 +257,58 @@
         }
     }
 
+    /** Returns the item index of the center item on this page so that we can restore to this
+     *  item index when we rotate. */
+    private int getMiddleComponentIndexOnCurrentPage() {
+        int i = -1;
+        if (getPageCount() > 0) {
+            int currentPage = getCurrentPage();
+            switch (mContentType) {
+            case Applications: {
+                PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(currentPage);
+                PagedViewCellLayoutChildren childrenLayout = layout.getChildrenLayout();
+                int numItemsPerPage = mCellCountX * mCellCountY;
+                int childCount = childrenLayout.getChildCount();
+                if (childCount > 0) {
+                    i = (currentPage * numItemsPerPage) + (childCount / 2);
+                }}
+                break;
+            case Widgets: {
+                PagedViewGridLayout layout = (PagedViewGridLayout) getChildAt(currentPage);
+                int numItemsPerPage = mWidgetCountX * mWidgetCountY;
+                int childCount = layout.getChildCount();
+                if (childCount > 0) {
+                    i = (currentPage * numItemsPerPage) + (childCount / 2);
+                }}
+                break;
+            }
+        }
+        return i;
+    }
+
+    /** Get the index of the item to restore to if we need to restore the current page. */
+    int getSaveInstanceStateIndex() {
+        if (mSaveInstanceStateItemIndex == -1) {
+            mSaveInstanceStateItemIndex = getMiddleComponentIndexOnCurrentPage();
+        }
+        return mSaveInstanceStateItemIndex;
+    }
+
+    /** Returns the page in the current orientation which is expected to contain the specified
+     *  item index. */
+    int getPageForComponent(int index) {
+        switch (mContentType) {
+        case Applications: {
+            int numItemsPerPage = mCellCountX * mCellCountY;
+            return (index / numItemsPerPage);
+        }
+        case Widgets: {
+            int numItemsPerPage = mWidgetCountX * mWidgetCountY;
+            return (index / numItemsPerPage);
+        }}
+        return -1;
+    }
+
     /**
      * This differs from isDataReady as this is the test done if isDataReady is not set.
      */
@@ -261,6 +318,20 @@
         return !mApps.isEmpty();
     }
 
+    /** Restores the page for an item at the specified index */
+    void restorePageForIndex(int index) {
+        if (index < 0) return;
+
+        int page = getPageForComponent(index);
+        if (page > -1) {
+            if (getChildCount() > 0) {
+                invalidatePageData(page);
+            } else {
+                mRestorePage = page;
+            }
+        }
+    }
+
     protected void onDataReady(int width, int height) {
         // Note that we transpose the counts in portrait so that we get a similar layout
         boolean isLandscape = getResources().getConfiguration().orientation ==
@@ -288,7 +359,8 @@
         int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST);
         mWidgetSpacingLayout.measure(widthSpec, heightSpec);
         mContentWidth = mWidgetSpacingLayout.getContentWidth();
-        invalidatePageData();
+        invalidatePageData(Math.max(0, mRestorePage));
+        mRestorePage = -1;
     }
 
     @Override
@@ -323,6 +395,19 @@
     }
 
     public void onPackagesUpdated() {
+        // TODO: this isn't ideal, but we actually need to delay here. This call is triggered
+        // by a broadcast receiver, and in order for it to work correctly, we need to know that
+        // the AppWidgetService has already received and processed the same broadcast. Since there
+        // is no guarantee about ordering of broadcast receipt, we just delay here. Ideally,
+        // we should have a more precise way of ensuring the AppWidgetService is up to date.
+        postDelayed(new Runnable() {
+           public void run() {
+               updatePackages();
+           }
+        }, 500);
+    }
+
+    public void updatePackages() {
         // Get the list of widgets and shortcuts
         boolean wasEmpty = mWidgets.isEmpty();
         mWidgets.clear();
@@ -489,7 +574,7 @@
 
     public void setContentType(ContentType type) {
         mContentType = type;
-        invalidatePageData(0);
+        invalidatePageData(0, true);
     }
 
     public boolean isContentType(ContentType type) {
@@ -536,7 +621,7 @@
             addView(layout);
         }
     }
-    public void syncAppsPageItems(int page) {
+    public void syncAppsPageItems(int page, boolean immediate) {
         // ensure that we have the right number of items on the pages
         int numPages = getPageCount();
         int numCells = mCellCountX * mCellCountY;
@@ -592,6 +677,10 @@
             return Process.THREAD_PRIORITY_DEFAULT;
         }
     }
+    private int getSleepForPage(int page) {
+        int pageDiff = Math.abs(page - mCurrentPage) - 1;
+        return Math.max(0, pageDiff * sPageSleepDelay);
+    }
     /**
      * Creates and executes a new AsyncTask to load a page of widget previews.
      */
@@ -612,37 +701,16 @@
             }
         }
 
+        // We introduce a slight delay to order the loading of side pages so that we don't thrash
+        final int sleepMs = getSleepForPage(page);
         AsyncTaskPageData pageData = new AsyncTaskPageData(page, widgets, cellWidth, cellHeight,
             cellCountX, new AsyncTaskCallback() {
                 @Override
                 public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
-                    // Ensure that this task starts running at the correct priority
-                    task.syncThreadPriority();
-
-                    // Load each of the widget/shortcut previews
-                    ArrayList<Object> items = data.items;
-                    ArrayList<Bitmap> images = data.generatedImages;
-                    int count = items.size();
-                    int cellWidth = data.cellWidth;
-                    int cellHeight = data.cellHeight;
-                    for (int i = 0; i < count && !task.isCancelled(); ++i) {
-                        // Before work on each item, ensure that this task is running at the correct
-                        // priority
-                        task.syncThreadPriority();
-
-                        Object rawInfo = items.get(i);
-                        if (rawInfo instanceof AppWidgetProviderInfo) {
-                            AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo;
-                            int[] cellSpans = CellLayout.rectToCell(getResources(),
-                                    info.minWidth, info.minHeight, null);
-                            images.add(getWidgetPreview(info, cellSpans[0],cellSpans[1],
-                                    cellWidth, cellHeight));
-                        } else if (rawInfo instanceof ResolveInfo) {
-                            // Fill in the shortcuts information
-                            ResolveInfo info = (ResolveInfo) rawInfo;
-                            images.add(getShortcutPreview(info, cellWidth, cellHeight));
-                        }
-                    }
+                    try {
+                        Thread.sleep(sleepMs);
+                    } catch (Exception e) {}
+                    loadWidgetPreviewsInBackground(task, data);
                 }
             },
             new AsyncTaskCallback() {
@@ -863,7 +931,7 @@
             addView(layout);
         }
     }
-    public void syncWidgetPageItems(int page) {
+    public void syncWidgetPageItems(int page, boolean immediate) {
         int numItemsPerPage = mWidgetCountX * mWidgetCountY;
         int contentWidth = mWidgetSpacingLayout.getContentWidth();
         int contentHeight = mWidgetSpacingLayout.getContentHeight();
@@ -880,7 +948,50 @@
             items.add(mWidgets.get(i));
         }
 
-        prepareLoadWidgetPreviewsTask(page, items, cellWidth, cellHeight, mWidgetCountX);
+        if (immediate) {
+            AsyncTaskPageData data = new AsyncTaskPageData(page, items, cellWidth, cellHeight,
+                    mWidgetCountX, null, null);
+            loadWidgetPreviewsInBackground(null, data);
+            onSyncWidgetPageItems(data);
+        } else {
+            prepareLoadWidgetPreviewsTask(page, items, cellWidth, cellHeight, mWidgetCountX);
+        }
+    }
+    private void loadWidgetPreviewsInBackground(AppsCustomizeAsyncTask task,
+            AsyncTaskPageData data) {
+        if (task != null) {
+            // Ensure that this task starts running at the correct priority
+            task.syncThreadPriority();
+        }
+
+        // Load each of the widget/shortcut previews
+        ArrayList<Object> items = data.items;
+        ArrayList<Bitmap> images = data.generatedImages;
+        int count = items.size();
+        int cellWidth = data.cellWidth;
+        int cellHeight = data.cellHeight;
+        for (int i = 0; i < count; ++i) {
+            if (task != null) {
+                // Ensure we haven't been cancelled yet
+                if (task.isCancelled()) break;
+                // Before work on each item, ensure that this task is running at the correct
+                // priority
+                task.syncThreadPriority();
+            }
+
+            Object rawInfo = items.get(i);
+            if (rawInfo instanceof AppWidgetProviderInfo) {
+                AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo;
+                int[] cellSpans = CellLayout.rectToCell(getResources(),
+                        info.minWidth, info.minHeight, null);
+                images.add(getWidgetPreview(info, cellSpans[0],cellSpans[1],
+                        cellWidth, cellHeight));
+            } else if (rawInfo instanceof ResolveInfo) {
+                // Fill in the shortcuts information
+                ResolveInfo info = (ResolveInfo) rawInfo;
+                images.add(getShortcutPreview(info, cellWidth, cellHeight));
+            }
+        }
     }
     private void onSyncWidgetPageItems(AsyncTaskPageData data) {
         int page = data.page;
@@ -991,13 +1102,13 @@
         }
     }
     @Override
-    public void syncPageItems(int page) {
+    public void syncPageItems(int page, boolean immediate) {
         switch (mContentType) {
         case Applications:
-            syncAppsPageItems(page);
+            syncAppsPageItems(page, immediate);
             break;
         case Widgets:
-            syncWidgetPageItems(page);
+            syncWidgetPageItems(page, immediate);
             break;
         }
     }
@@ -1036,6 +1147,10 @@
         setChildrenDrawnWithCacheEnabled(false);
         */
         super.onPageEndMoving();
+
+        // We reset the save index when we change pages so that it will be recalculated on next
+        // rotation
+        mSaveInstanceStateItemIndex = -1;
     }
 
     /*
diff --git a/src/com/android/launcher2/AppsCustomizeTabHost.java b/src/com/android/launcher2/AppsCustomizeTabHost.java
index 6bd4c03..d6ae145 100644
--- a/src/com/android/launcher2/AppsCustomizeTabHost.java
+++ b/src/com/android/launcher2/AppsCustomizeTabHost.java
@@ -18,15 +18,19 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewPropertyAnimator;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.ImageView;
 import android.widget.TabHost;
 import android.widget.TabWidget;
 import android.widget.TextView;
@@ -44,6 +48,7 @@
     private ViewGroup mTabs;
     private ViewGroup mTabsContainer;
     private AppsCustomizePagedView mAppsCustomizePane;
+    private ImageView mAnimationBuffer;
 
     private boolean mInTransition;
     private boolean mResetAfterTransition;
@@ -89,6 +94,7 @@
         mTabs = tabs;
         mTabsContainer = tabsContainer;
         mAppsCustomizePane = appsCustomizePane;
+        mAnimationBuffer = (ImageView) findViewById(R.id.animation_buffer);
         if (tabs == null || mAppsCustomizePane == null) throw new Resources.NotFoundException();
 
         // Configure the tabs content factory to return the same paged view (that we change the
@@ -168,31 +174,53 @@
             final Resources res = getResources();
             final int duration = res.getInteger(R.integer.config_tabTransitionDuration);
 
-            ObjectAnimator anim = ObjectAnimator.ofFloat(mAppsCustomizePane, "alpha", 0f);
-            anim.setDuration(duration);
-            anim.addListener(new AnimatorListenerAdapter() {
+            // We post a runnable here because there is a delay while the first page is loading and
+            // the feedback from having changed the tab almost feels better than having it stick
+            post(new Runnable() {
                 @Override
-                public void onAnimationStart(android.animation.Animator animation) {
+                public void run() {
+                    // Setup the animation buffer
+                    Bitmap b = Bitmap.createBitmap(mAppsCustomizePane.getMeasuredWidth(),
+                            mAppsCustomizePane.getMeasuredHeight(), Bitmap.Config.ARGB_8888);
+                    Canvas c = new Canvas(b);
+                    mAppsCustomizePane.draw(c);
+                    mAppsCustomizePane.setAlpha(0f);
+                    mAnimationBuffer.setImageBitmap(b);
+                    mAnimationBuffer.setAlpha(1f);
+                    mAnimationBuffer.setVisibility(View.VISIBLE);
+                    c.setBitmap(null);
+                    b = null;
+
+                    // Toggle the new content
                     onTabChangedStart();
-                }
-                @Override
-                public void onAnimationEnd(android.animation.Animator animation) {
                     onTabChangedEnd(type);
 
-                    ObjectAnimator anim = ObjectAnimator.ofFloat(mAppsCustomizePane, "alpha", 1f);
-                    anim.setDuration(duration);
-                    anim.addListener(new AnimatorListenerAdapter() {
+                    // Animate the transition
+                    ObjectAnimator outAnim = ObjectAnimator.ofFloat(mAnimationBuffer, "alpha", 0f);
+                    outAnim.addListener(new AnimatorListenerAdapter() {
                         @Override
-                        public void onAnimationEnd(android.animation.Animator animation) {
+                        public void onAnimationEnd(Animator animation) {
+                            mAnimationBuffer.setVisibility(View.GONE);
+                            mAnimationBuffer.setImageBitmap(null);
+                        }
+                    });
+                    ObjectAnimator inAnim = ObjectAnimator.ofFloat(mAppsCustomizePane, "alpha", 1f);
+                    inAnim.addListener(new AnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
                             if (!LauncherApplication.isScreenLarge()) {
                                 mAppsCustomizePane.flashScrollingIndicator();
                             }
+                            mAppsCustomizePane.loadAssociatedPages(
+                                    mAppsCustomizePane.getCurrentPage());
                         }
                     });
-                    anim.start();
+                    AnimatorSet animSet = new AnimatorSet();
+                    animSet.playTogether(outAnim, inAnim);
+                    animSet.setDuration(duration);
+                    animSet.start();
                 }
             });
-            anim.start();
         }
     }
 
diff --git a/src/com/android/launcher2/CellLayout.java b/src/com/android/launcher2/CellLayout.java
index 6f59d1f..1841713 100644
--- a/src/com/android/launcher2/CellLayout.java
+++ b/src/com/android/launcher2/CellLayout.java
@@ -792,27 +792,35 @@
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
+        // First we clear the tag to ensure that on every touch down we start with a fresh slate,
+        // even in the case where we return early. Not clearing here was causing bugs whereby on
+        // long-press we'd end up picking up an item from a previous drag operation.
+        final int action = ev.getAction();
+
+        if (action == MotionEvent.ACTION_DOWN) {
+            clearTagCellInfo();
+        }
+
         if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
             return true;
         }
-        final int action = ev.getAction();
-        final CellInfo cellInfo = mCellInfo;
 
         if (action == MotionEvent.ACTION_DOWN) {
             setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY());
-        } else if (action == MotionEvent.ACTION_UP) {
-            cellInfo.cell = null;
-            cellInfo.cellX = -1;
-            cellInfo.cellY = -1;
-            cellInfo.spanX = 0;
-            cellInfo.spanY = 0;
-            setTag(cellInfo);
         }
-
         return false;
     }
 
-    @Override
+    private void clearTagCellInfo() {
+        final CellInfo cellInfo = mCellInfo;
+        cellInfo.cell = null;
+        cellInfo.cellX = -1;
+        cellInfo.cellY = -1;
+        cellInfo.spanX = 0;
+        cellInfo.spanY = 0;
+        setTag(cellInfo);
+    }
+
     public CellInfo getTag() {
         return (CellInfo) super.getTag();
     }
diff --git a/src/com/android/launcher2/Folder.java b/src/com/android/launcher2/Folder.java
index c490d5e..1fcfebc 100644
--- a/src/com/android/launcher2/Folder.java
+++ b/src/com/android/launcher2/Folder.java
@@ -297,13 +297,19 @@
         ArrayList<ShortcutInfo> children = info.contents;
         ArrayList<ShortcutInfo> overflow = new ArrayList<ShortcutInfo>();
         setupContentForNumItems(children.size());
+        int count = 0;
         for (int i = 0; i < children.size(); i++) {
             ShortcutInfo child = (ShortcutInfo) children.get(i);
             if (!createAndAddShortcut(child)) {
                 overflow.add(child);
+            } else {
+                count++;
             }
         }
 
+        // We rearrange the items in case there are any empty gaps
+        setupContentForNumItems(count);
+
         // If our folder has too many items we prune them from the list. This is an issue 
         // when upgrading from the old Folders implementation which could contain an unlimited
         // number of items.
@@ -507,8 +513,8 @@
         // by another item. If it is, we need to find the next available slot and assign
         // it that position. This is an issue when upgrading from the old Folders implementation
         // which could contain an unlimited number of items.
-        if (mContent.getChildAt(item.cellX, item.cellY) != null ||
-                item.cellX < 0 || item.cellY < 0) {
+        if (mContent.getChildAt(item.cellX, item.cellY) != null || item.cellX < 0 || item.cellY < 0
+                || item.cellX >= mContent.getCountX() || item.cellY >= mContent.getCountY()) {
             if (!findAndSetEmptyCells(item)) {
                 return false;
             }
diff --git a/src/com/android/launcher2/FolderIcon.java b/src/com/android/launcher2/FolderIcon.java
index 10928c0..f1a1508 100644
--- a/src/com/android/launcher2/FolderIcon.java
+++ b/src/com/android/launcher2/FolderIcon.java
@@ -321,44 +321,53 @@
             float scaleRelativeToDragLayer, int index, Runnable postAnimationRunnable) {
         item.cellX = -1;
         item.cellY = -1;
-        DragLayer dragLayer = mLauncher.getDragLayer();
-        Rect from = new Rect();
-        dragLayer.getViewRectRelativeToSelf(animateView, from);
-        Rect to = finalRect;
-        if (to == null) {
-            to = new Rect();
-            Workspace workspace = mLauncher.getWorkspace();
-            // Set cellLayout and this to it's final state to compute final animation locations
-            workspace.setFinalTransitionTransform((CellLayout) getParent().getParent());
-            float scaleX = getScaleX();
-            float scaleY = getScaleY();
-            setScaleX(1.0f);
-            setScaleY(1.0f);
-            scaleRelativeToDragLayer = dragLayer.getDescendantRectRelativeToSelf(this, to);
-            // Finished computing final animation locations, restore current state
-            setScaleX(scaleX);
-            setScaleY(scaleY);
-            workspace.resetTransitionTransform((CellLayout) getParent().getParent());
-        }
 
-        int[] center = new int[2];
-        float scale = getLocalCenterForIndex(index, center);
-        center[0] = (int) Math.round(scaleRelativeToDragLayer * center[0]);
-        center[1] = (int) Math.round(scaleRelativeToDragLayer * center[1]);
-
-        to.offset(center[0] - animateView.getMeasuredWidth() / 2,
-                center[1] - animateView.getMeasuredHeight() / 2);
-
-        float finalAlpha = index < NUM_ITEMS_IN_PREVIEW ? 0.5f : 0f;
-
-        dragLayer.animateView(animateView, from, to, finalAlpha, scale * scaleRelativeToDragLayer,
-                DROP_IN_ANIMATION_DURATION, new DecelerateInterpolator(2),
-                new AccelerateInterpolator(2), postAnimationRunnable, false);
-        postDelayed(new Runnable() {
-            public void run() {
-                addItem(item);
+        // Typically, the animateView corresponds to the DragView; however, if this is being done
+        // after a configuration activity (ie. for a Shortcut being dragged from AllApps) we
+        // will not have a view to animate
+        if (animateView != null) {
+            DragLayer dragLayer = mLauncher.getDragLayer();
+            Rect from = new Rect();
+            dragLayer.getViewRectRelativeToSelf(animateView, from);
+            Rect to = finalRect;
+            if (to == null) {
+                to = new Rect();
+                Workspace workspace = mLauncher.getWorkspace();
+                // Set cellLayout and this to it's final state to compute final animation locations
+                workspace.setFinalTransitionTransform((CellLayout) getParent().getParent());
+                float scaleX = getScaleX();
+                float scaleY = getScaleY();
+                setScaleX(1.0f);
+                setScaleY(1.0f);
+                scaleRelativeToDragLayer = dragLayer.getDescendantRectRelativeToSelf(this, to);
+                // Finished computing final animation locations, restore current state
+                setScaleX(scaleX);
+                setScaleY(scaleY);
+                workspace.resetTransitionTransform((CellLayout) getParent().getParent());
             }
-        }, DROP_IN_ANIMATION_DURATION);
+
+            int[] center = new int[2];
+            float scale = getLocalCenterForIndex(index, center);
+            center[0] = (int) Math.round(scaleRelativeToDragLayer * center[0]);
+            center[1] = (int) Math.round(scaleRelativeToDragLayer * center[1]);
+
+            to.offset(center[0] - animateView.getMeasuredWidth() / 2,
+                    center[1] - animateView.getMeasuredHeight() / 2);
+
+            float finalAlpha = index < NUM_ITEMS_IN_PREVIEW ? 0.5f : 0f;
+
+            dragLayer.animateView(animateView, from, to, finalAlpha,
+                    scale * scaleRelativeToDragLayer, DROP_IN_ANIMATION_DURATION,
+                    new DecelerateInterpolator(2), new AccelerateInterpolator(2),
+                    postAnimationRunnable, false);
+            postDelayed(new Runnable() {
+                public void run() {
+                    addItem(item);
+                }
+            }, DROP_IN_ANIMATION_DURATION);
+        } else {
+            addItem(item);
+        }
     }
 
     public void onDrop(DragObject d) {
diff --git a/src/com/android/launcher2/IconCache.java b/src/com/android/launcher2/IconCache.java
index 247e164..7f3ae86 100644
--- a/src/com/android/launcher2/IconCache.java
+++ b/src/com/android/launcher2/IconCache.java
@@ -160,13 +160,14 @@
         }
     }
 
-    public Bitmap getIcon(ComponentName component, ResolveInfo resolveInfo) {
+    public Bitmap getIcon(ComponentName component, ResolveInfo resolveInfo,
+            HashMap<Object, CharSequence> labelCache) {
         synchronized (mCache) {
             if (resolveInfo == null || component == null) {
                 return null;
             }
 
-            CacheEntry entry = cacheLocked(component, resolveInfo, null);
+            CacheEntry entry = cacheLocked(component, resolveInfo, labelCache);
             return entry.icon;
         }
     }
diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java
index 7ab41bc..a0601e0 100644
--- a/src/com/android/launcher2/Launcher.java
+++ b/src/com/android/launcher2/Launcher.java
@@ -40,7 +40,6 @@
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.Intent.ShortcutIconResource;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -51,7 +50,6 @@
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.AsyncTask;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.Handler;
@@ -87,6 +85,7 @@
 
 import com.android.common.Search;
 import com.android.launcher.R;
+import com.android.launcher2.DropTarget.DragObject;
 
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
@@ -670,10 +669,12 @@
                 mAppsCustomizeContent.setContentType(
                         mAppsCustomizeTabHost.getContentTypeForTabTag(curTab));
                 mAppsCustomizeTabHost.setCurrentTabByTag(curTab);
+                mAppsCustomizeContent.loadAssociatedPages(
+                        mAppsCustomizeContent.getCurrentPage());
             }
 
-            // Note: currently we do not restore the page for the AppsCustomize pane because the
-            // change in layout can drastically affect the saved page index
+            int currentIndex = savedState.getInt("apps_customize_currentIndex");
+            mAppsCustomizeContent.restorePageForIndex(currentIndex);
         }
     }
 
@@ -799,11 +800,25 @@
 
         boolean foundCellSpan = false;
 
+        ShortcutInfo info = mModel.infoFromShortcutIntent(this, data, null);
+        final View view = createShortcut(info);
+
         // First we check if we already know the exact location where we want to add this item.
         if (cellX >= 0 && cellY >= 0) {
             cellXY[0] = cellX;
             cellXY[1] = cellY;
             foundCellSpan = true;
+
+            // If appropriate, either create a folder or add to an existing folder
+            if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY,
+                    true, null,null)) {
+                return;
+            }
+            DragObject dragObject = new DragObject();
+            dragObject.dragInfo = info;
+            if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, dragObject, true)) {
+                return;
+            }
         } else if (touchXY != null) {
             // when dragging and dropping, just find the closest free spot
             int[] result = layout.findNearestVacantArea(touchXY[0], touchXY[1], 1, 1, cellXY);
@@ -817,11 +832,9 @@
             return;
         }
 
-        final ShortcutInfo info = mModel.addShortcut(
-                this, data, container, screen, cellXY[0], cellXY[1], false);
+        LauncherModel.addItemToDatabase(this, info, container, screen, cellXY[0], cellXY[1], false);
 
         if (!mRestoring) {
-            final View view = createShortcut(info);
             mWorkspace.addInScreen(view, container, screen, cellXY[0], cellXY[1], 1, 1,
                     isWorkspaceLocked());
         }
@@ -1132,6 +1145,8 @@
             if (currentTabTag != null) {
                 outState.putString("apps_customize_currentTab", currentTabTag);
             }
+            int currentIndex = mAppsCustomizeContent.getSaveInstanceStateIndex();
+            outState.putInt("apps_customize_currentIndex", currentIndex);
         }
     }
 
diff --git a/src/com/android/launcher2/LauncherModel.java b/src/com/android/launcher2/LauncherModel.java
index 547d51f..f14140c 100644
--- a/src/com/android/launcher2/LauncherModel.java
+++ b/src/com/android/launcher2/LauncherModel.java
@@ -661,11 +661,13 @@
         private boolean mStopped;
         private boolean mLoadAndBindStepFinished;
         private HashMap<Object, CharSequence> mLabelCache;
+        private HashMap<Object, byte[]> mDbIconCache;
 
         LoaderTask(Context context, boolean isLaunching) {
             mContext = context;
             mIsLaunching = isLaunching;
             mLabelCache = new HashMap<Object, CharSequence>();
+            mDbIconCache = new HashMap<Object, byte[]>();
         }
 
         boolean isLaunching() {
@@ -731,10 +733,15 @@
             final Callbacks cbk = mCallbacks.get();
             final boolean loadWorkspaceFirst = cbk != null ? (!cbk.isAllAppsVisible()) : true;
 
+            // We update the icons in the database afterwards in case they have changed
+            mDbIconCache.clear();
+
             keep_running: {
                 // Elevate priority when Home launches for the first time to avoid
                 // starving at boot time. Staring at a blank home is not cool.
                 synchronized (mLock) {
+                    if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to " +
+                            (mIsLaunching ? "DEFAULT" : "BACKGROUND"));
                     android.os.Process.setThreadPriority(mIsLaunching
                             ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND);
                 }
@@ -754,6 +761,7 @@
                 // settled down.
                 synchronized (mLock) {
                     if (mIsLaunching) {
+                        if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to BACKGROUND");
                         android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                     }
                 }
@@ -769,6 +777,14 @@
                 }
             }
 
+
+            // Update the saved icons if necessary
+            if (DEBUG_LOADERS) Log.d(TAG, "Comparing loaded icons to database icons");
+            for (Object key : mDbIconCache.keySet()) {
+                updateSavedIcon(mContext, (ShortcutInfo) key, mDbIconCache.get(key));
+            }
+            mDbIconCache.clear();
+
             // Clear out this reference, otherwise we end up holding it until all of the
             // callback runnables are done.
             mContext = null;
@@ -970,7 +986,7 @@
 
                                 // now that we've loaded everthing re-save it with the
                                 // icon in case it disappears somehow.
-                                updateSavedIcon(context, info, c, iconIndex);
+                                queueIconToBeChecked(mDbIconCache, info, c, iconIndex);
                             } else {
                                 // Failed to load the shortcut, probably because the
                                 // activity manager couldn't resolve it (maybe the app
@@ -1522,7 +1538,7 @@
         // have icons anyway.
         final ResolveInfo resolveInfo = manager.resolveActivity(intent, 0);
         if (resolveInfo != null) {
-            icon = mIconCache.getIcon(componentName, resolveInfo);
+            icon = mIconCache.getIcon(componentName, resolveInfo, labelCache);
         }
         // the db
         if (icon == null) {
@@ -1750,10 +1766,11 @@
         return info;
     }
 
-    void updateSavedIcon(Context context, ShortcutInfo info, Cursor c, int iconIndex) {
+    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) {
-            return;
+            return false;
         }
         // If this icon doesn't have a custom icon, check to see
         // what's stored in the DB, and if it doesn't match what
@@ -1762,25 +1779,29 @@
         // package manager can't find an icon (for example because
         // the app is on SD) then we can use that instead.
         if (!info.customIcon && !info.usingFallbackIcon) {
-            boolean needSave;
-            byte[] data = c.getBlob(iconIndex);
-            try {
-                if (data != null) {
-                    Bitmap saved = BitmapFactory.decodeByteArray(data, 0, data.length);
-                    Bitmap loaded = info.getIcon(mIconCache);
-                    needSave = !saved.sameAs(loaded);
-                } else {
-                    needSave = true;
-                }
-            } catch (Exception e) {
+            cache.put(info, c.getBlob(iconIndex));
+            return true;
+        }
+        return false;
+    }
+    void updateSavedIcon(Context context, ShortcutInfo info, byte[] data) {
+        boolean needSave = false;
+        try {
+            if (data != null) {
+                Bitmap saved = BitmapFactory.decodeByteArray(data, 0, data.length);
+                Bitmap loaded = info.getIcon(mIconCache);
+                needSave = !saved.sameAs(loaded);
+            } else {
                 needSave = true;
             }
-            if (needSave) {
-                Log.d(TAG, "going to save icon bitmap for info=" + info);
-                // This is slower than is ideal, but this only happens once
-                // or when the app is updated with a new icon.
-                updateItemInDatabase(context, info);
-            }
+        } catch (Exception e) {
+            needSave = true;
+        }
+        if (needSave) {
+            Log.d(TAG, "going to save icon bitmap for info=" + info);
+            // This is slower than is ideal, but this only happens once
+            // or when the app is updated with a new icon.
+            updateItemInDatabase(context, info);
         }
     }
 
diff --git a/src/com/android/launcher2/PagedView.java b/src/com/android/launcher2/PagedView.java
index d2d734c..40e2328 100644
--- a/src/com/android/launcher2/PagedView.java
+++ b/src/com/android/launcher2/PagedView.java
@@ -1494,7 +1494,10 @@
         };
     }
 
-    public void loadAssociatedPages(int page) {
+    protected void loadAssociatedPages(int page) {
+        loadAssociatedPages(page, false);
+    }
+    protected void loadAssociatedPages(int page, boolean immediateAndOnly) {
         if (mContentIsRefreshable) {
             final int count = getChildCount();
             if (page < count) {
@@ -1503,11 +1506,14 @@
                 if (DEBUG) Log.d(TAG, "loadAssociatedPages: " + lowerPageBound + "/"
                         + upperPageBound);
                 for (int i = 0; i < count; ++i) {
+                    if ((i != page) && immediateAndOnly) {
+                        continue;
+                    }
                     Page layout = (Page) getChildAt(i);
                     final int childCount = layout.getPageChildCount();
                     if (lowerPageBound <= i && i <= upperPageBound) {
                         if (mDirtyPageContent.get(i)) {
-                            syncPageItems(i);
+                            syncPageItems(i, (i == page) && immediateAndOnly);
                             mDirtyPageContent.set(i, false);
                         }
                     } else {
@@ -1607,7 +1613,7 @@
      * This method is called to synchronize the items that are on a particular page.  If views on
      * the page can be reused, then they should be updated within this method.
      */
-    public abstract void syncPageItems(int page);
+    public abstract void syncPageItems(int page, boolean immediate);
 
     protected void postInvalidatePageData(final boolean clearViews) {
         post(new Runnable() {
@@ -1622,9 +1628,12 @@
     }
 
     protected void invalidatePageData() {
-        invalidatePageData(-1);
+        invalidatePageData(-1, false);
     }
     protected void invalidatePageData(int currentPage) {
+        invalidatePageData(currentPage, false);
+    }
+    protected void invalidatePageData(int currentPage, boolean immediateAndOnly) {
         if (!mIsDataReady) {
             return;
         }
@@ -1640,7 +1649,7 @@
 
             // Set a new page as the current page if necessary
             if (currentPage > -1) {
-                setCurrentPage(currentPage);
+                setCurrentPage(Math.min(getPageCount() - 1, currentPage));
             }
 
             // Mark each of the pages as dirty
@@ -1651,7 +1660,7 @@
             }
 
             // Load any pages that are necessary for the current window of views
-            loadAssociatedPages(mCurrentPage);
+            loadAssociatedPages(mCurrentPage, immediateAndOnly);
             mDirtyPageAlpha = true;
             updateAdjacentPagesAlpha();
             requestLayout();
diff --git a/src/com/android/launcher2/PagedViewCellLayout.java b/src/com/android/launcher2/PagedViewCellLayout.java
index 63cf9e8..803e700 100644
--- a/src/com/android/launcher2/PagedViewCellLayout.java
+++ b/src/com/android/launcher2/PagedViewCellLayout.java
@@ -192,6 +192,10 @@
         return mChildren.getChildCount();
     }
 
+    public PagedViewCellLayoutChildren getChildrenLayout() {
+        return mChildren;
+    }
+
     @Override
     public View getChildOnPageAt(int i) {
         return mChildren.getChildAt(i);
diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java
index 947c946..b6a1666 100644
--- a/src/com/android/launcher2/Workspace.java
+++ b/src/com/android/launcher2/Workspace.java
@@ -2208,8 +2208,15 @@
             sourceInfo.cellX = -1;
             sourceInfo.cellY = -1;
 
-            fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale,
-                    postAnimationRunnable);
+            // If the dragView is null, we can't animate
+            boolean animate = dragView != null;
+            if (animate) {
+                fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale,
+                        postAnimationRunnable);
+            } else {
+                fi.addItem(destInfo);
+                fi.addItem(sourceInfo);
+            }
             return true;
         }
         return false;
@@ -2996,8 +3003,21 @@
         if (info instanceof PendingAddItemInfo) {
             final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) dragInfo;
 
-            mTargetCell = findNearestVacantArea(touchXY[0], touchXY[1], spanX, spanY, null,
-                    cellLayout, mTargetCell);
+            boolean findNearestVacantCell = true;
+            if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
+                mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
+                        cellLayout, mTargetCell);
+                if (willCreateUserFolder((ItemInfo) d.dragInfo, mDragTargetLayout, mTargetCell,
+                        true) || willAddToExistingUserFolder((ItemInfo) d.dragInfo,
+                                mDragTargetLayout, mTargetCell)) {
+                    findNearestVacantCell = false;
+                }
+            }
+            if (findNearestVacantCell) {
+                    mTargetCell = findNearestVacantArea(touchXY[0], touchXY[1], spanX, spanY, null,
+                        cellLayout, mTargetCell);
+            }
+
             Runnable onAnimationCompleteRunnable = new Runnable() {
                 @Override
                 public void run() {
@@ -3544,7 +3564,7 @@
     }
 
     @Override
-    public void syncPageItems(int page) {
+    public void syncPageItems(int page, boolean immediate) {
     }
 
     @Override