Reconcile with jb-mr0-release jb-release

Change-Id: I3742aff353335629ae76eacc2d92a6aed87b1ebe
diff --git a/res/layout-land/drop_target_bar.xml b/res/layout-land/drop_target_bar.xml
deleted file mode 100644
index 794a79e..0000000
--- a/res/layout-land/drop_target_bar.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
-    <FrameLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        style="@style/DropTargetButtonContainer"
-        android:layout_weight="1">
-        <!-- Delete target -->
-        <com.android.launcher2.DeleteDropTarget
-            style="@style/DropTargetButton"
-            android:id="@+id/delete_target_text"
-            android:drawableTop="@drawable/remove_target_selector" />
-    </FrameLayout>
-    <FrameLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        style="@style/DropTargetButtonContainer"
-        android:layout_weight="1">
-        <!-- Info target -->
-        <com.android.launcher2.InfoDropTarget
-            style="@style/DropTargetButton"
-            android:id="@+id/info_target_text"
-            android:drawableTop="@drawable/info_target_selector" />
-    </FrameLayout>
-</merge>
\ No newline at end of file
diff --git a/res/layout-port/drop_target_bar.xml b/res/layout/drop_target_bar.xml
similarity index 100%
rename from res/layout-port/drop_target_bar.xml
rename to res/layout/drop_target_bar.xml
diff --git a/res/values-sw600dp/config.xml b/res/values-sw600dp/config.xml
index 02789de..b67b9c1 100644
--- a/res/values-sw600dp/config.xml
+++ b/res/values-sw600dp/config.xml
@@ -1,9 +1,10 @@
 <resources>
+    <bool name="allow_rotation">true</bool>
+
     <integer name="cell_count_x">6</integer>
     <integer name="cell_count_y">6</integer>
     <integer name="hotseat_cell_count">7</integer>
     <integer name="hotseat_all_apps_index">3</integer>
-
 <!-- DragController -->
     <integer name="config_flingToDeleteMinVelocity">-1000</integer>
 
diff --git a/res/values-sw720dp/config.xml b/res/values-sw720dp/config.xml
index 97a6e9f..cdca805 100644
--- a/res/values-sw720dp/config.xml
+++ b/res/values-sw720dp/config.xml
@@ -1,7 +1,6 @@
 <resources>
     <bool name="config_largeHeap">true</bool>
     <bool name="is_large_screen">true</bool>
-    <bool name="allow_rotation">true</bool>
 
 <!-- AllApps/Customize/AppsCustomize -->
     <!-- Out of 100, the percent to shrink the workspace during spring loaded mode. -->
diff --git a/src/com/android/launcher2/AppWidgetResizeFrame.java b/src/com/android/launcher2/AppWidgetResizeFrame.java
index 8824686..f94ad01 100644
--- a/src/com/android/launcher2/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher2/AppWidgetResizeFrame.java
@@ -53,6 +53,9 @@
     private int mBackgroundPadding;
     private int mTouchTargetWidth;
 
+    private int mTopTouchRegionAdjustment = 0;
+    private int mBottomTouchRegionAdjustment = 0;
+
     int[] mDirectionVector = new int[2];
 
     final int SNAP_DURATION = 150;
@@ -139,10 +142,12 @@
     public boolean beginResizeIfPointInRegion(int x, int y) {
         boolean horizontalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0;
         boolean verticalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0;
+
         mLeftBorderActive = (x < mTouchTargetWidth) && horizontalActive;
         mRightBorderActive = (x > getWidth() - mTouchTargetWidth) && horizontalActive;
-        mTopBorderActive = (y < mTouchTargetWidth) && verticalActive;
-        mBottomBorderActive = (y > getHeight() - mTouchTargetWidth) && verticalActive;
+        mTopBorderActive = (y < mTouchTargetWidth + mTopTouchRegionAdjustment) && verticalActive;
+        mBottomBorderActive = (y > getHeight() - mTouchTargetWidth + mBottomTouchRegionAdjustment)
+                && verticalActive;
 
         boolean anyBordersActive = mLeftBorderActive || mRightBorderActive
                 || mTopBorderActive || mBottomBorderActive;
@@ -378,13 +383,20 @@
         int newX = mWidgetView.getLeft() - mBackgroundPadding + xOffset + mWidgetPaddingLeft;
         int newY = mWidgetView.getTop() - mBackgroundPadding + yOffset + mWidgetPaddingTop;
 
-        // We need to make sure the frame stays within the bounds of the CellLayout
+        // We need to make sure the frame's touchable regions lie fully within the bounds of the 
+        // DragLayer. We allow the actual handles to be clipped, but we shift the touch regions
+        // down accordingly to provide a proper touch target.
         if (newY < 0) {
-            newHeight -= -newY;
-            newY = 0;
+            // In this case we shift the touch region down to start at the top of the DragLayer
+            mTopTouchRegionAdjustment = -newY;
+        } else {
+            mTopTouchRegionAdjustment = 0;
         }
         if (newY + newHeight > mDragLayer.getHeight()) {
-            newHeight -= newY + newHeight - mDragLayer.getHeight();
+            // In this case we shift the touch region up to end at the bottom of the DragLayer
+            mBottomTouchRegionAdjustment = -(newY + newHeight - mDragLayer.getHeight());
+        } else {
+            mBottomTouchRegionAdjustment = 0;
         }
 
         if (!animate) {
diff --git a/src/com/android/launcher2/AppsCustomizePagedView.java b/src/com/android/launcher2/AppsCustomizePagedView.java
index a508361..8cb169e 100644
--- a/src/com/android/launcher2/AppsCustomizePagedView.java
+++ b/src/com/android/launcher2/AppsCustomizePagedView.java
@@ -528,18 +528,22 @@
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
     }
 
-    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. This is a
-        // workaround until we add a callback from AppWidgetService to AppWidgetHost when widget
-        // packages are added, updated or removed.
-        postDelayed(new Runnable() {
-           public void run() {
-               updatePackages();
-           }
-        }, 1500);
+    public void onPackagesUpdated(boolean immediate) {
+        if (immediate) {
+            updatePackages();
+        } else {
+            // 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. This is a
+            // workaround until we add a callback from AppWidgetService to AppWidgetHost when widget
+            // packages are added, updated or removed.
+            postDelayed(new Runnable() {
+               public void run() {
+                   updatePackages();
+               }
+            }, 1500);
+        }
     }
 
     public void updatePackages() {
@@ -578,7 +582,7 @@
     @Override
     public void onClick(View v) {
         // When we have exited all apps or are in transition, disregard clicks
-        if (!mLauncher.isAllAppsCustomizeOpen() ||
+        if (!mLauncher.isAllAppsVisible() ||
                 mLauncher.getWorkspace().isSwitchingState()) return;
 
         if (v instanceof PagedViewIcon) {
diff --git a/src/com/android/launcher2/AppsCustomizeTabHost.java b/src/com/android/launcher2/AppsCustomizeTabHost.java
index 9fa2f32..144aad9 100644
--- a/src/com/android/launcher2/AppsCustomizeTabHost.java
+++ b/src/com/android/launcher2/AppsCustomizeTabHost.java
@@ -49,7 +49,6 @@
     private ViewGroup mTabs;
     private ViewGroup mTabsContainer;
     private AppsCustomizePagedView mAppsCustomizePane;
-    private boolean mSuppressContentCallback = false;
     private FrameLayout mAnimationBuffer;
     private LinearLayout mContent;
 
@@ -81,17 +80,18 @@
      * reflects the new content (but doesn't do the animation and logic associated with changing
      * tabs manually).
      */
-    private void setContentTypeImmediate(AppsCustomizePagedView.ContentType type) {
+    void setContentTypeImmediate(AppsCustomizePagedView.ContentType type) {
+        setOnTabChangedListener(null);
         onTabChangedStart();
         onTabChangedEnd(type);
+        setCurrentTabByTag(getTabTagForContentType(type));
+        setOnTabChangedListener(this);
     }
     void selectAppsTab() {
         setContentTypeImmediate(AppsCustomizePagedView.ContentType.Applications);
-        setCurrentTabByTag(APPS_TAB_TAG);
     }
     void selectWidgetsTab() {
         setContentTypeImmediate(AppsCustomizePagedView.ContentType.Widgets);
-        setCurrentTabByTag(WIDGETS_TAB_TAG);
     }
 
     /**
@@ -158,10 +158,11 @@
             if (contentWidth > 0 && mTabs.getLayoutParams().width != contentWidth) {
                 // Set the width and show the tab bar
                 mTabs.getLayoutParams().width = contentWidth;
-                post(mRelayoutAndMakeVisible);
+                mRelayoutAndMakeVisible.run();
             }
+
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         }
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
     }
 
      public boolean onInterceptTouchEvent(MotionEvent ev) {
@@ -207,10 +208,6 @@
     @Override
     public void onTabChanged(String tabId) {
         final AppsCustomizePagedView.ContentType type = getContentTypeForTabTag(tabId);
-        if (mSuppressContentCallback) {
-            mSuppressContentCallback = false;
-            return;
-        }
 
         // Animate the changing of the tab content by fading pages in and out
         final Resources res = getResources();
@@ -301,8 +298,9 @@
     }
 
     public void setCurrentTabFromContent(AppsCustomizePagedView.ContentType type) {
-        mSuppressContentCallback = true;
+        setOnTabChangedListener(null);
         setCurrentTabByTag(getTabTagForContentType(type));
+        setOnTabChangedListener(this);
     }
 
     /**
diff --git a/src/com/android/launcher2/CellLayout.java b/src/com/android/launcher2/CellLayout.java
index c028ff1..97d9bc1 100644
--- a/src/com/android/launcher2/CellLayout.java
+++ b/src/com/android/launcher2/CellLayout.java
@@ -17,9 +17,9 @@
 package com.android.launcher2;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
-import android.animation.AnimatorListenerAdapter;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
@@ -37,8 +37,10 @@
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.NinePatchDrawable;
+import android.os.Parcelable;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.SparseArray;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewDebug;
@@ -532,6 +534,10 @@
         return false;
     }
 
+    public void restoreInstanceState(SparseArray<Parcelable> states) {
+        dispatchRestoreInstanceState(states);
+    }
+
     @Override
     public void cancelLongPress() {
         super.cancelLongPress();
diff --git a/src/com/android/launcher2/DeferredHandler.java b/src/com/android/launcher2/DeferredHandler.java
index 930da56..b7e48b1 100644
--- a/src/com/android/launcher2/DeferredHandler.java
+++ b/src/com/android/launcher2/DeferredHandler.java
@@ -98,6 +98,18 @@
         }
     }
 
+    /** Runs all queued Runnables from the calling thread. */
+    public void flush() {
+        LinkedList<Runnable> queue = new LinkedList<Runnable>();
+        synchronized (mQueue) {
+            queue.addAll(mQueue);
+            mQueue.clear();
+        }
+        for (Runnable r : queue) {
+            r.run();
+        }
+    }
+
     void scheduleNextLocked() {
         if (mQueue.size() > 0) {
             Runnable peek = mQueue.getFirst();
diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java
index f4180cd..38085e0 100644
--- a/src/com/android/launcher2/Launcher.java
+++ b/src/com/android/launcher2/Launcher.java
@@ -181,7 +181,7 @@
             "com.android.launcher.toolbar_voice_search_icon";
 
     /** The different states that Launcher can be in. */
-    private enum State { WORKSPACE, APPS_CUSTOMIZE, APPS_CUSTOMIZE_SPRING_LOADED };
+    private enum State { NONE, WORKSPACE, APPS_CUSTOMIZE, APPS_CUSTOMIZE_SPRING_LOADED };
     private State mState = State.WORKSPACE;
     private AnimatorSet mStateAnimation;
     private AnimatorSet mDividerAnimator;
@@ -229,6 +229,10 @@
     private boolean mAutoAdvanceRunning = false;
 
     private Bundle mSavedState;
+    // We set the state in both onCreate and then onNewIntent in some cases, which causes both
+    // scroll issues (because the workspace may not have been measured yet) and extra work.
+    // Instead, just save the state that we need to restore Launcher to, and commit it in onResume.
+    private State mOnResumeState = State.NONE;
 
     private SpannableStringBuilder mDefaultKeySsb = null;
 
@@ -239,6 +243,9 @@
     private boolean mWaitingForResult;
     private boolean mOnResumeNeedsLoad;
 
+    // Keep track of whether the user has left launcher
+    private static boolean sPausedFromUserAction = false;
+
     private Bundle mSavedInstanceState;
 
     private LauncherModel mModel;
@@ -271,6 +278,8 @@
     private static Drawable.ConstantState[] sVoiceSearchIcon = new Drawable.ConstantState[2];
     private static Drawable.ConstantState[] sAppMarketIcon = new Drawable.ConstantState[2];
 
+    private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<Integer>();
+
     static final ArrayList<String> sDumpLogs = new ArrayList<String>();
 
     // We only want to get the SharedPreferences once since it does an FS stat each time we get
@@ -338,6 +347,11 @@
         mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
         mAppWidgetHost.startListening();
 
+        // If we are getting an onCreate, we can actually preempt onResume and unset mPaused here,
+        // this also ensures that any synchronous binding below doesn't re-trigger another
+        // LauncherModel load.
+        mPaused = false;
+
         if (PROFILE_STARTUP) {
             android.os.Debug.startMethodTracing(
                     Environment.getExternalStorageDirectory() + "/launcher");
@@ -357,7 +371,7 @@
 
         // Update customization drawer _after_ restoring the states
         if (mAppsCustomizeContent != null) {
-            mAppsCustomizeContent.onPackagesUpdated();
+            mAppsCustomizeContent.onPackagesUpdated(true);
         }
 
         if (PROFILE_STARTUP) {
@@ -365,7 +379,15 @@
         }
 
         if (!mRestoring) {
-            mModel.startLoader(true);
+            if (sPausedFromUserAction) {
+                // If the user leaves launcher, then we should just load items asynchronously when
+                // they return.
+                mModel.startLoader(true, -1);
+            } else {
+                // We only load the page synchronously if the user rotates (or triggers a
+                // configuration change) while launcher is in the foreground
+                mModel.startLoader(true, mWorkspace.getCurrentPage());
+            }
         }
 
         if (!mModel.isAllAppsLoaded()) {
@@ -386,6 +408,11 @@
         unlockScreenOrientation(true);
     }
 
+    protected void onUserLeaveHint() {
+        super.onUserLeaveHint();
+        sPausedFromUserAction = true;
+    }
+
     private void updateGlobalIcons() {
         boolean searchVisible = false;
         boolean voiceVisible = false;
@@ -671,13 +698,22 @@
     protected void onResume() {
         super.onResume();
 
+        // Restore the previous launcher state
+        if (mOnResumeState == State.WORKSPACE) {
+            showWorkspace(false);
+        } else if (mOnResumeState == State.APPS_CUSTOMIZE) {
+            showAllApps(false);
+        }
+        mOnResumeState = State.NONE;
+
         // Process any items that were added while Launcher was away
         InstallShortcutReceiver.flushInstallQueue(this);
 
         mPaused = false;
+        sPausedFromUserAction = false;
         if (mRestoring || mOnResumeNeedsLoad) {
             mWorkspaceLoading = true;
-            mModel.startLoader(true);
+            mModel.startLoader(true, -1);
             mRestoring = false;
             mOnResumeNeedsLoad = false;
         }
@@ -818,7 +854,7 @@
 
         State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal()));
         if (state == State.APPS_CUSTOMIZE) {
-            showAllApps(false);
+            mOnResumeState = State.APPS_CUSTOMIZE;
         }
 
         int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN, -1);
@@ -854,10 +890,8 @@
         if (mAppsCustomizeTabHost != null) {
             String curTab = savedState.getString("apps_customize_currentTab");
             if (curTab != null) {
-                // We set this directly so that there is no delay before the tab is set
-                mAppsCustomizeContent.setContentType(
+                mAppsCustomizeTabHost.setContentTypeImmediate(
                         mAppsCustomizeTabHost.getContentTypeForTabTag(curTab));
-                mAppsCustomizeTabHost.setCurrentTabByTag(curTab);
                 mAppsCustomizeContent.loadAssociatedPages(
                         mAppsCustomizeContent.getCurrentPage());
             }
@@ -1355,7 +1389,14 @@
 
             closeFolder();
             exitSpringLoadedDragMode();
-            showWorkspace(alreadyOnHome);
+
+            // If we are already on home, then just animate back to the workspace, otherwise, just
+            // wait until onResume to set the state back to Workspace
+            if (alreadyOnHome) {
+                showWorkspace(true);
+            } else {
+                mOnResumeState = State.WORKSPACE;
+            }
 
             final View v = getWindow().peekDecorView();
             if (v != null && v.getWindowToken() != null) {
@@ -1372,14 +1413,16 @@
     }
 
     @Override
-    protected void onRestoreInstanceState(Bundle savedInstanceState) {
-        // Do not call super here
-        mSavedInstanceState = savedInstanceState;
+    public void onRestoreInstanceState(Bundle state) {
+        super.onRestoreInstanceState(state);
+        for (int page: mSynchronouslyBoundPages) {
+            mWorkspace.restoreInstanceStateForChild(page);
+        }
     }
 
     @Override
     protected void onSaveInstanceState(Bundle outState) {
-        outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getCurrentPage());
+        outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getNextPage());
         super.onSaveInstanceState(outState);
 
         outState.putInt(RUNTIME_STATE, mState.ordinal());
@@ -1763,7 +1806,7 @@
 
     @Override
     public void onBackPressed() {
-        if (mState == State.APPS_CUSTOMIZE) {
+        if (isAllAppsVisible()) {
             showWorkspace(true);
         } else if (mWorkspace.getOpenFolder() != null) {
             Folder openFolder = mWorkspace.getOpenFolder();
@@ -1836,7 +1879,7 @@
                 handleFolderClick(fi);
             }
         } else if (v == mAllAppsButton) {
-            if (mState == State.APPS_CUSTOMIZE) {
+            if (isAllAppsVisible()) {
                 showWorkspace(true);
             } else {
                 onClickAllAppsButton(v);
@@ -2240,7 +2283,7 @@
 
     // Now a part of LauncherModel.Callbacks. Used to reorder loading steps.
     public boolean isAllAppsVisible() {
-        return (mState == State.APPS_CUSTOMIZE);
+        return (mState == State.APPS_CUSTOMIZE) || (mOnResumeState == State.APPS_CUSTOMIZE);
     }
 
     public boolean isAllAppsButtonRank(int rank) {
@@ -2267,7 +2310,7 @@
 
     void disableWallpaperIfInAllApps() {
         // Only disable it if we are in all apps
-        if (mState == State.APPS_CUSTOMIZE) {
+        if (isAllAppsVisible()) {
             if (mAppsCustomizeTabHost != null &&
                     !mAppsCustomizeTabHost.isTransitioning()) {
                 updateWallpaperVisibility(false);
@@ -2708,7 +2751,7 @@
     }
 
     void enterSpringLoadedDragMode() {
-        if (mState == State.APPS_CUSTOMIZE) {
+        if (isAllAppsVisible()) {
             hideAppsCustomizeHelper(State.APPS_CUSTOMIZE_SPRING_LOADED, true, true, null);
             hideDockDivider();
             mState = State.APPS_CUSTOMIZE_SPRING_LOADED;
@@ -2782,10 +2825,6 @@
         // TODO
     }
 
-    public boolean isAllAppsCustomizeOpen() {
-        return mState == State.APPS_CUSTOMIZE;
-    }
-
     /**
      * Shows the hotseat area.
      */
@@ -3273,6 +3312,10 @@
         }
     }
 
+    public void onPageBoundSynchronously(int page) {
+        mSynchronouslyBoundPages.add(page);
+    }
+
     /**
      * Callback saying that there aren't any more items to bind.
      *
@@ -3288,10 +3331,7 @@
             mSavedState = null;
         }
 
-        if (mSavedInstanceState != null) {
-            super.onRestoreInstanceState(mSavedInstanceState);
-            mSavedInstanceState = null;
-        }
+        mWorkspace.restoreInstanceStateForRemainingPages();
 
         // If we received the result of any pending adds while the loader was running (e.g. the
         // widget configuration forced an orientation change), process them now.
@@ -3414,23 +3454,30 @@
      * Implementation of the method from LauncherModel.Callbacks.
      */
     public void bindAllApplications(final ArrayList<ApplicationInfo> apps) {
+        Runnable setAllAppsRunnable = new Runnable() {
+            public void run() {
+                if (mAppsCustomizeContent != null) {
+                    mAppsCustomizeContent.setApps(apps);
+                }
+            }
+        };
+
         // Remove the progress bar entirely; we could also make it GONE
         // but better to remove it since we know it's not going to be used
         View progressBar = mAppsCustomizeTabHost.
             findViewById(R.id.apps_customize_progress_bar);
         if (progressBar != null) {
             ((ViewGroup)progressBar.getParent()).removeView(progressBar);
+
+            // We just post the call to setApps so the user sees the progress bar
+            // disappear-- otherwise, it just looks like the progress bar froze
+            // which doesn't look great
+            mAppsCustomizeTabHost.post(setAllAppsRunnable);
+        } else {
+            // If we did not initialize the spinner in onCreate, then we can directly set the
+            // list of applications without waiting for any progress bars views to be hidden.
+            setAllAppsRunnable.run();
         }
-        // We just post the call to setApps so the user sees the progress bar
-        // disappear-- otherwise, it just looks like the progress bar froze
-        // which doesn't look great
-        mAppsCustomizeTabHost.post(new Runnable() {
-            public void run() {
-                if (mAppsCustomizeContent != null) {
-                    mAppsCustomizeContent.setApps(apps);
-                }
-            }
-        });
     }
 
     /**
@@ -3485,7 +3532,7 @@
      */
     public void bindPackagesUpdated() {
         if (mAppsCustomizeContent != null) {
-            mAppsCustomizeContent.onPackagesUpdated();
+            mAppsCustomizeContent.onPackagesUpdated(false);
         }
     }
 
diff --git a/src/com/android/launcher2/LauncherModel.java b/src/com/android/launcher2/LauncherModel.java
index fc1a26d..92be7e4 100644
--- a/src/com/android/launcher2/LauncherModel.java
+++ b/src/com/android/launcher2/LauncherModel.java
@@ -57,7 +57,10 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Maintains in-memory state of the Launcher. It is expected that there should be only one
@@ -77,6 +80,7 @@
     private final Object mLock = new Object();
     private DeferredHandler mHandler = new DeferredHandler();
     private LoaderTask mLoaderTask;
+    private boolean mIsLoaderTaskRunning;
 
     private static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
     static {
@@ -90,29 +94,41 @@
     private boolean mWorkspaceLoaded;
     private boolean mAllAppsLoaded;
 
+    // When we are loading pages synchronously, we can't just post the binding of items on the side
+    // pages as this delays the rotation process.  Instead, we wait for a callback from the first
+    // draw (in Workspace) to initiate the binding of the remaining side pages.  Any time we start
+    // a normal load, we also clear this set of Runnables.
+    static final ArrayList<Runnable> mDeferredBindRunnables = new ArrayList<Runnable>();
+
     private WeakReference<Callbacks> mCallbacks;
 
     // < only access in worker thread >
     private AllAppsList mAllAppsList;
 
-    // sItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by
-    // LauncherModel to their ids
-    static final HashMap<Long, ItemInfo> sItemsIdMap = new HashMap<Long, ItemInfo>();
+    // The lock that must be acquired before referencing any static bg data structures.  Unlike
+    // other locks, this one can generally be held long-term because we never expect any of these
+    // static data structures to be referenced outside of the worker thread except on the first
+    // load after configuration change.
+    static final Object sBgLock = new Object();
 
-    // sItems is passed to bindItems, which expects a list of all folders and shortcuts created by
+    // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by
+    // LauncherModel to their ids
+    static final HashMap<Long, ItemInfo> sBgItemsIdMap = new HashMap<Long, ItemInfo>();
+
+    // sBgItems is passed to bindItems, which expects a list of all folders and shortcuts created by
     //       LauncherModel that are directly on the home screen (however, no widgets or shortcuts
     //       within folders).
-    static final ArrayList<ItemInfo> sWorkspaceItems = new ArrayList<ItemInfo>();
+    static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>();
 
-    // sAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
-    static final ArrayList<LauncherAppWidgetInfo> sAppWidgets =
+    // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
+    static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets =
         new ArrayList<LauncherAppWidgetInfo>();
 
-    // sFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
-    static final HashMap<Long, FolderInfo> sFolders = new HashMap<Long, FolderInfo>();
+    // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
+    static final HashMap<Long, FolderInfo> sBgFolders = new HashMap<Long, FolderInfo>();
 
-    // sDbIconCache is the set of ItemInfos that need to have their icons updated in the database
-    static final HashMap<Object, byte[]> sDbIconCache = new HashMap<Object, byte[]>();
+    // sBgDbIconCache is the set of ItemInfos that need to have their icons updated in the database
+    static final HashMap<Object, byte[]> sBgDbIconCache = new HashMap<Object, byte[]>();
 
     // </ only access in worker thread >
 
@@ -140,6 +156,7 @@
         public boolean isAllAppsVisible();
         public boolean isAllAppsButtonRank(int rank);
         public void bindSearchablesChanged();
+        public void onPageBoundSynchronously(int page);
     }
 
     LauncherModel(LauncherApplication app, IconCache iconCache) {
@@ -158,6 +175,28 @@
         mPreviousConfigMcc = config.mcc;
     }
 
+    /** Runs the specified runnable immediately if called from the main thread, otherwise it is
+     * posted on the main thread handler. */
+    private void runOnMainThread(Runnable r) {
+        if (sWorkerThread.getThreadId() == Process.myTid()) {
+            // If we are on the worker thread, post onto the main handler
+            mHandler.post(r);
+        } else {
+            r.run();
+        }
+    }
+
+    /** Runs the specified runnable immediately if called from the worker thread, otherwise it is
+     * posted on the worker thread handler. */
+    private static void runOnWorkerThread(Runnable r) {
+        if (sWorkerThread.getThreadId() == Process.myTid()) {
+            r.run();
+        } else {
+            // If we are not on the worker thread, then post to the worker handler
+            sWorker.post(r);
+        }
+    }
+
     public Bitmap getFallbackIcon() {
         return Bitmap.createBitmap(mDefaultIcon);
     }
@@ -171,26 +210,28 @@
         });
     }
 
-    /** Unbinds all the sWorkspaceItems on the main thread, and return a copy of sWorkspaceItems
-     * that is save to reference from the main thread. */
-    private ArrayList<ItemInfo> unbindWorkspaceItemsOnMainThread() {
+    /** Unbinds all the sBgWorkspaceItems and sBgAppWidgets on the main thread */
+    private void unbindWorkspaceItemsOnMainThread() {
         // Ensure that we don't use the same workspace items data structure on the main thread
         // by making a copy of workspace items first.
-        final ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>(sWorkspaceItems);
-        final ArrayList<ItemInfo> appWidgets = new ArrayList<ItemInfo>(sAppWidgets);
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-               for (ItemInfo item : workspaceItems) {
-                   item.unbind();
-               }
-               for (ItemInfo item : appWidgets) {
-                   item.unbind();
-               }
-            }
-        });
-
-        return workspaceItems;
+        final ArrayList<ItemInfo> tmpWorkspaceItems = new ArrayList<ItemInfo>();
+        final ArrayList<ItemInfo> tmpAppWidgets = new ArrayList<ItemInfo>();
+        synchronized (sBgLock) {
+            tmpWorkspaceItems.addAll(sBgWorkspaceItems);
+            tmpAppWidgets.addAll(sBgAppWidgets);
+        }
+        Runnable r = new Runnable() {
+                @Override
+                public void run() {
+                   for (ItemInfo item : tmpWorkspaceItems) {
+                       item.unbind();
+                   }
+                   for (ItemInfo item : tmpAppWidgets) {
+                       item.unbind();
+                   }
+                }
+            };
+        runOnMainThread(r);
     }
 
     /**
@@ -218,35 +259,35 @@
             public void run() {
                 cr.update(uri, values, null, null);
 
-                ItemInfo modelItem = sItemsIdMap.get(itemId);
-                if (item != modelItem) {
-                    // the modelItem needs to match up perfectly with item if our model is to be
-                    // consistent with the database-- for now, just require modelItem == item
-                    String msg = "item: " + ((item != null) ? item.toString() : "null") +
-                        "modelItem: " + ((modelItem != null) ? modelItem.toString() : "null") +
-                        "Error: ItemInfo passed to " + callingFunction + " doesn't match original";
-                    throw new RuntimeException(msg);
-                }
-
-                // Items are added/removed from the corresponding FolderInfo elsewhere, such
-                // as in Workspace.onDrop. Here, we just add/remove them from the list of items
-                // that are on the desktop, as appropriate
-                if (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
-                        modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
-                    if (!sWorkspaceItems.contains(modelItem)) {
-                        sWorkspaceItems.add(modelItem);
+                // Lock on mBgLock *after* the db operation
+                synchronized (sBgLock) {
+                    ItemInfo modelItem = sBgItemsIdMap.get(itemId);
+                    if (item != modelItem) {
+                        // the modelItem needs to match up perfectly with item if our model is to be
+                        // consistent with the database-- for now, just require modelItem == item
+                        String msg = "item: " + ((item != null) ? item.toString() : "null") +
+                            "modelItem: " + ((modelItem != null) ? modelItem.toString() : "null") +
+                            "Error: ItemInfo passed to " + callingFunction + " doesn't match " +
+                            "original";
+                        throw new RuntimeException(msg);
                     }
-                } else {
-                    sWorkspaceItems.remove(modelItem);
+
+                    // Items are added/removed from the corresponding FolderInfo elsewhere, such
+                    // as in Workspace.onDrop. Here, we just add/remove them from the list of items
+                    // that are on the desktop, as appropriate
+                    if (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
+                            modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+                        if (!sBgWorkspaceItems.contains(modelItem)) {
+                            sBgWorkspaceItems.add(modelItem);
+
+                        }
+                    } else {
+                        sBgWorkspaceItems.remove(modelItem);
+                    }
                 }
             }
         };
-
-        if (sWorkerThread.getThreadId() == Process.myTid()) {
-            r.run();
-        } else {
-            sWorker.post(r);
-        }
+        runOnWorkerThread(r);
     }
 
     /**
@@ -450,36 +491,33 @@
             public void run() {
                 cr.insert(notify ? LauncherSettings.Favorites.CONTENT_URI :
                         LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, values);
-
-                if (sItemsIdMap.containsKey(item.id)) {
-                    // we should not be adding new items in the db with the same id
-                    throw new RuntimeException("Error: ItemInfo id (" + item.id + ") passed to " +
-                        "addItemToDatabase already exists." + item.toString());
-                }
-                sItemsIdMap.put(item.id, item);
-                switch (item.itemType) {
-                    case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
-                        sFolders.put(item.id, (FolderInfo) item);
-                        // Fall through
-                    case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
-                    case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
-                        if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
-                                item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
-                            sWorkspaceItems.add(item);
-                        }
-                        break;
-                    case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
-                        sAppWidgets.add((LauncherAppWidgetInfo) item);
-                        break;
+                // Lock on mBgLock *after* the db operation
+                synchronized (sBgLock) {
+                    if (sBgItemsIdMap.containsKey(item.id)) {
+                        // we should not be adding new items in the db with the same id
+                        throw new RuntimeException("Error: ItemInfo id (" + item.id + ") passed to " +
+                            "addItemToDatabase already exists." + item.toString());
+                    }
+                    sBgItemsIdMap.put(item.id, item);
+                    switch (item.itemType) {
+                        case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+                            sBgFolders.put(item.id, (FolderInfo) item);
+                            // Fall through
+                        case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+                        case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                            if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
+                                    item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+                                sBgWorkspaceItems.add(item);
+                            }
+                            break;
+                        case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+                            sBgAppWidgets.add((LauncherAppWidgetInfo) item);
+                            break;
+                    }
                 }
             }
         };
-
-        if (sWorkerThread.getThreadId() == Process.myTid()) {
-            r.run();
-        } else {
-            sWorker.post(r);
-        }
+        runOnWorkerThread(r);
     }
 
     /**
@@ -519,28 +557,27 @@
         Runnable r = new Runnable() {
             public void run() {
                 cr.delete(uriToDelete, null, null);
-                switch (item.itemType) {
-                    case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
-                        sFolders.remove(item.id);
-                        sWorkspaceItems.remove(item);
-                        break;
-                    case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
-                    case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
-                        sWorkspaceItems.remove(item);
-                        break;
-                    case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
-                        sAppWidgets.remove((LauncherAppWidgetInfo) item);
-                        break;
+                // Lock on mBgLock *after* the db operation
+                synchronized (sBgLock) {
+                    switch (item.itemType) {
+                        case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+                            sBgFolders.remove(item.id);
+                            sBgWorkspaceItems.remove(item);
+                            break;
+                        case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+                        case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                            sBgWorkspaceItems.remove(item);
+                            break;
+                        case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+                            sBgAppWidgets.remove((LauncherAppWidgetInfo) item);
+                            break;
+                    }
+                    sBgItemsIdMap.remove(item.id);
+                    sBgDbIconCache.remove(item);
                 }
-                sItemsIdMap.remove(item.id);
-                sDbIconCache.remove(item);
             }
         };
-        if (sWorkerThread.getThreadId() == Process.myTid()) {
-            r.run();
-        } else {
-            sWorker.post(r);
-        }
+        runOnWorkerThread(r);
     }
 
     /**
@@ -552,24 +589,26 @@
         Runnable r = new Runnable() {
             public void run() {
                 cr.delete(LauncherSettings.Favorites.getContentUri(info.id, false), null, null);
-                sItemsIdMap.remove(info.id);
-                sFolders.remove(info.id);
-                sDbIconCache.remove(info);
-                sWorkspaceItems.remove(info);
+                // Lock on mBgLock *after* the db operation
+                synchronized (sBgLock) {
+                    sBgItemsIdMap.remove(info.id);
+                    sBgFolders.remove(info.id);
+                    sBgDbIconCache.remove(info);
+                    sBgWorkspaceItems.remove(info);
+                }
 
                 cr.delete(LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION,
                         LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
-                for (ItemInfo childInfo : info.contents) {
-                    sItemsIdMap.remove(childInfo.id);
-                    sDbIconCache.remove(childInfo);
+                // Lock on mBgLock *after* the db operation
+                synchronized (sBgLock) {
+                    for (ItemInfo childInfo : info.contents) {
+                        sBgItemsIdMap.remove(childInfo.id);
+                        sBgDbIconCache.remove(childInfo);
+                    }
                 }
             }
         };
-        if (sWorkerThread.getThreadId() == Process.myTid()) {
-            r.run();
-        } else {
-            sWorker.post(r);
-        }
+        runOnWorkerThread(r);
     }
 
     /**
@@ -697,7 +736,7 @@
             }
         }
         if (runLoader) {
-            startLoader(false);
+            startLoader(false, -1);
         }
     }
 
@@ -715,24 +754,42 @@
         return isLaunching;
     }
 
-    public void startLoader(boolean isLaunching) {
+    public void startLoader(boolean isLaunching, int synchronousBindPage) {
         synchronized (mLock) {
             if (DEBUG_LOADERS) {
                 Log.d(TAG, "startLoader isLaunching=" + isLaunching);
             }
 
+            // Clear any deferred bind-runnables from the synchronized load process
+            // We must do this before any loading/binding is scheduled below.
+            mDeferredBindRunnables.clear();
+
             // Don't bother to start the thread if we know it's not going to do anything
             if (mCallbacks != null && mCallbacks.get() != null) {
                 // If there is already one running, tell it to stop.
                 // also, don't downgrade isLaunching if we're already running
                 isLaunching = isLaunching || stopLoaderLocked();
                 mLoaderTask = new LoaderTask(mApp, isLaunching);
-                sWorkerThread.setPriority(Thread.NORM_PRIORITY);
-                sWorker.post(mLoaderTask);
+                if (synchronousBindPage > -1 && mAllAppsLoaded && mWorkspaceLoaded) {
+                    mLoaderTask.runBindSynchronousPage(synchronousBindPage);
+                } else {
+                    sWorkerThread.setPriority(Thread.NORM_PRIORITY);
+                    sWorker.post(mLoaderTask);
+                }
             }
         }
     }
 
+    void bindRemainingSynchronousPages() {
+        // Post the remaining side pages to be loaded
+        if (!mDeferredBindRunnables.isEmpty()) {
+            for (final Runnable r : mDeferredBindRunnables) {
+                mHandler.post(r);
+            }
+            mDeferredBindRunnables.clear();
+        }
+    }
+
     public void stopLoader() {
         synchronized (mLock) {
             if (mLoaderTask != null) {
@@ -762,11 +819,11 @@
      */
     private class LoaderTask implements Runnable {
         private Context mContext;
-        private Thread mWaitThread;
         private boolean mIsLaunching;
         private boolean mIsLoadingAndBindingWorkspace;
         private boolean mStopped;
         private boolean mLoadAndBindStepFinished;
+
         private HashMap<Object, CharSequence> mLabelCache;
 
         LoaderTask(Context context, boolean isLaunching) {
@@ -802,7 +859,7 @@
             }
 
             // Bind the workspace
-            bindWorkspace();
+            bindWorkspace(-1);
         }
 
         private void waitForIdle() {
@@ -839,7 +896,46 @@
             }
         }
 
+        void runBindSynchronousPage(int synchronousBindPage) {
+            if (synchronousBindPage < 0) {
+                // Ensure that we have a valid page index to load synchronously
+                throw new RuntimeException("Should not call runBindSynchronousPage() without " +
+                        "valid page index");
+            }
+            if (!mAllAppsLoaded || !mWorkspaceLoaded) {
+                // Ensure that we don't try and bind a specified page when the pages have not been
+                // loaded already (we should load everything asynchronously in that case)
+                throw new RuntimeException("Expecting AllApps and Workspace to be loaded");
+            }
+            synchronized (mLock) {
+                if (mIsLoaderTaskRunning) {
+                    // Ensure that we are never running the background loading at this point since
+                    // we also touch the background collections
+                    throw new RuntimeException("Error! Background loading is already running");
+                }
+            }
+
+            // XXX: Throw an exception if we are already loading (since we touch the worker thread
+            //      data structures, we can't allow any other thread to touch that data, but because
+            //      this call is synchronous, we can get away with not locking).
+
+            // The LauncherModel is static in the LauncherApplication and mHandler may have queued
+            // operations from the previous activity.  We need to ensure that all queued operations
+            // are executed before any synchronous binding work is done.
+            mHandler.flush();
+
+            // Divide the set of loaded items into those that we are binding synchronously, and
+            // everything else that is to be bound normally (asynchronously).
+            bindWorkspace(synchronousBindPage);
+            // XXX: For now, continue posting the binding of AllApps as there are other issues that
+            //      arise from that.
+            onlyBindAllApps();
+        }
+
         public void run() {
+            synchronized (mLock) {
+                mIsLoaderTaskRunning = true;
+            }
             // Optimize for end-user experience: if the Launcher is up and // running with the
             // All Apps interface in the foreground, load All Apps first. Otherwise, load the
             // workspace first (default).
@@ -895,10 +991,12 @@
 
             // Update the saved icons if necessary
             if (DEBUG_LOADERS) Log.d(TAG, "Comparing loaded icons to database icons");
-            for (Object key : sDbIconCache.keySet()) {
-                updateSavedIcon(mContext, (ShortcutInfo) key, sDbIconCache.get(key));
+            synchronized (sBgLock) {
+                for (Object key : sBgDbIconCache.keySet()) {
+                    updateSavedIcon(mContext, (ShortcutInfo) key, sBgDbIconCache.get(key));
+                }
+                sBgDbIconCache.clear();
             }
-            sDbIconCache.clear();
 
             // Clear out this reference, otherwise we end up holding it until all of the
             // callback runnables are done.
@@ -909,6 +1007,7 @@
                 if (mLoaderTask == this) {
                     mLoaderTask = null;
                 }
+                mIsLoaderTaskRunning = false;
             }
         }
 
@@ -1008,278 +1107,365 @@
             // Make sure the default workspace is loaded, if needed
             mApp.getLauncherProvider().loadDefaultFavoritesIfNecessary();
 
-            sWorkspaceItems.clear();
-            sAppWidgets.clear();
-            sFolders.clear();
-            sItemsIdMap.clear();
-            sDbIconCache.clear();
+            synchronized (sBgLock) {
+                sBgWorkspaceItems.clear();
+                sBgAppWidgets.clear();
+                sBgFolders.clear();
+                sBgItemsIdMap.clear();
+                sBgDbIconCache.clear();
 
-            final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
+                final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
 
-            final Cursor c = contentResolver.query(
-                    LauncherSettings.Favorites.CONTENT_URI, null, null, null, null);
+                final Cursor c = contentResolver.query(
+                        LauncherSettings.Favorites.CONTENT_URI, null, null, null, null);
 
-            // +1 for the hotseat (it can be larger than the workspace)
-            // Load workspace in reverse order to ensure that latest items are loaded first (and
-            // before any earlier duplicates)
-            final ItemInfo occupied[][][] =
-                    new ItemInfo[Launcher.SCREEN_COUNT + 1][mCellCountX + 1][mCellCountY + 1];
+                // +1 for the hotseat (it can be larger than the workspace)
+                // Load workspace in reverse order to ensure that latest items are loaded first (and
+                // before any earlier duplicates)
+                final ItemInfo occupied[][][] =
+                        new ItemInfo[Launcher.SCREEN_COUNT + 1][mCellCountX + 1][mCellCountY + 1];
 
-            try {
-                final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
-                final int intentIndex = c.getColumnIndexOrThrow
-                        (LauncherSettings.Favorites.INTENT);
-                final int titleIndex = c.getColumnIndexOrThrow
-                        (LauncherSettings.Favorites.TITLE);
-                final int iconTypeIndex = c.getColumnIndexOrThrow(
-                        LauncherSettings.Favorites.ICON_TYPE);
-                final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
-                final int iconPackageIndex = c.getColumnIndexOrThrow(
-                        LauncherSettings.Favorites.ICON_PACKAGE);
-                final int iconResourceIndex = c.getColumnIndexOrThrow(
-                        LauncherSettings.Favorites.ICON_RESOURCE);
-                final int containerIndex = c.getColumnIndexOrThrow(
-                        LauncherSettings.Favorites.CONTAINER);
-                final int itemTypeIndex = c.getColumnIndexOrThrow(
-                        LauncherSettings.Favorites.ITEM_TYPE);
-                final int appWidgetIdIndex = c.getColumnIndexOrThrow(
-                        LauncherSettings.Favorites.APPWIDGET_ID);
-                final int screenIndex = c.getColumnIndexOrThrow(
-                        LauncherSettings.Favorites.SCREEN);
-                final int cellXIndex = c.getColumnIndexOrThrow
-                        (LauncherSettings.Favorites.CELLX);
-                final int cellYIndex = c.getColumnIndexOrThrow
-                        (LauncherSettings.Favorites.CELLY);
-                final int spanXIndex = c.getColumnIndexOrThrow
-                        (LauncherSettings.Favorites.SPANX);
-                final int spanYIndex = c.getColumnIndexOrThrow(
-                        LauncherSettings.Favorites.SPANY);
-                //final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
-                //final int displayModeIndex = c.getColumnIndexOrThrow(
-                //        LauncherSettings.Favorites.DISPLAY_MODE);
+                try {
+                    final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
+                    final int intentIndex = c.getColumnIndexOrThrow
+                            (LauncherSettings.Favorites.INTENT);
+                    final int titleIndex = c.getColumnIndexOrThrow
+                            (LauncherSettings.Favorites.TITLE);
+                    final int iconTypeIndex = c.getColumnIndexOrThrow(
+                            LauncherSettings.Favorites.ICON_TYPE);
+                    final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
+                    final int iconPackageIndex = c.getColumnIndexOrThrow(
+                            LauncherSettings.Favorites.ICON_PACKAGE);
+                    final int iconResourceIndex = c.getColumnIndexOrThrow(
+                            LauncherSettings.Favorites.ICON_RESOURCE);
+                    final int containerIndex = c.getColumnIndexOrThrow(
+                            LauncherSettings.Favorites.CONTAINER);
+                    final int itemTypeIndex = c.getColumnIndexOrThrow(
+                            LauncherSettings.Favorites.ITEM_TYPE);
+                    final int appWidgetIdIndex = c.getColumnIndexOrThrow(
+                            LauncherSettings.Favorites.APPWIDGET_ID);
+                    final int screenIndex = c.getColumnIndexOrThrow(
+                            LauncherSettings.Favorites.SCREEN);
+                    final int cellXIndex = c.getColumnIndexOrThrow
+                            (LauncherSettings.Favorites.CELLX);
+                    final int cellYIndex = c.getColumnIndexOrThrow
+                            (LauncherSettings.Favorites.CELLY);
+                    final int spanXIndex = c.getColumnIndexOrThrow
+                            (LauncherSettings.Favorites.SPANX);
+                    final int spanYIndex = c.getColumnIndexOrThrow(
+                            LauncherSettings.Favorites.SPANY);
+                    //final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
+                    //final int displayModeIndex = c.getColumnIndexOrThrow(
+                    //        LauncherSettings.Favorites.DISPLAY_MODE);
 
-                ShortcutInfo info;
-                String intentDescription;
-                LauncherAppWidgetInfo appWidgetInfo;
-                int container;
-                long id;
-                Intent intent;
+                    ShortcutInfo info;
+                    String intentDescription;
+                    LauncherAppWidgetInfo appWidgetInfo;
+                    int container;
+                    long id;
+                    Intent intent;
 
-                while (!mStopped && c.moveToNext()) {
-                    try {
-                        int itemType = c.getInt(itemTypeIndex);
+                    while (!mStopped && c.moveToNext()) {
+                        try {
+                            int itemType = c.getInt(itemTypeIndex);
 
-                        switch (itemType) {
-                        case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
-                        case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
-                            intentDescription = c.getString(intentIndex);
-                            try {
-                                intent = Intent.parseUri(intentDescription, 0);
-                            } catch (URISyntaxException e) {
-                                continue;
-                            }
+                            switch (itemType) {
+                            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+                            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                                intentDescription = c.getString(intentIndex);
+                                try {
+                                    intent = Intent.parseUri(intentDescription, 0);
+                                } catch (URISyntaxException e) {
+                                    continue;
+                                }
 
-                            if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
-                                info = getShortcutInfo(manager, intent, context, c, iconIndex,
-                                        titleIndex, mLabelCache);
-                            } else {
-                                info = getShortcutInfo(c, context, iconTypeIndex,
-                                        iconPackageIndex, iconResourceIndex, iconIndex,
-                                        titleIndex);
+                                if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
+                                    info = getShortcutInfo(manager, intent, context, c, iconIndex,
+                                            titleIndex, mLabelCache);
+                                } else {
+                                    info = getShortcutInfo(c, context, iconTypeIndex,
+                                            iconPackageIndex, iconResourceIndex, iconIndex,
+                                            titleIndex);
 
-                                // App shortcuts that used to be automatically added to Launcher
-                                // didn't always have the correct intent flags set, so do that here
-                                if (intent.getAction() != null &&
+                                    // App shortcuts that used to be automatically added to Launcher
+                                    // didn't always have the correct intent flags set, so do that
+                                    // here
+                                    if (intent.getAction() != null &&
                                         intent.getCategories() != null &&
                                         intent.getAction().equals(Intent.ACTION_MAIN) &&
                                         intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
-                                    intent.addFlags(
-                                        Intent.FLAG_ACTIVITY_NEW_TASK |
-                                        Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+                                        intent.addFlags(
+                                            Intent.FLAG_ACTIVITY_NEW_TASK |
+                                            Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+                                    }
                                 }
-                            }
 
-                            if (info != null) {
-                                info.intent = intent;
-                                info.id = c.getLong(idIndex);
+                                if (info != null) {
+                                    info.intent = intent;
+                                    info.id = c.getLong(idIndex);
+                                    container = c.getInt(containerIndex);
+                                    info.container = container;
+                                    info.screen = c.getInt(screenIndex);
+                                    info.cellX = c.getInt(cellXIndex);
+                                    info.cellY = c.getInt(cellYIndex);
+
+                                    // check & update map of what's occupied
+                                    if (!checkItemPlacement(occupied, info)) {
+                                        break;
+                                    }
+
+                                    switch (container) {
+                                    case LauncherSettings.Favorites.CONTAINER_DESKTOP:
+                                    case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
+                                        sBgWorkspaceItems.add(info);
+                                        break;
+                                    default:
+                                        // Item is in a user folder
+                                        FolderInfo folderInfo =
+                                                findOrMakeFolder(sBgFolders, container);
+                                        folderInfo.add(info);
+                                        break;
+                                    }
+                                    sBgItemsIdMap.put(info.id, info);
+
+                                    // 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;
+
+                            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+                                id = c.getLong(idIndex);
+                                FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
+
+                                folderInfo.title = c.getString(titleIndex);
+                                folderInfo.id = id;
                                 container = c.getInt(containerIndex);
-                                info.container = container;
-                                info.screen = c.getInt(screenIndex);
-                                info.cellX = c.getInt(cellXIndex);
-                                info.cellY = c.getInt(cellYIndex);
+                                folderInfo.container = container;
+                                folderInfo.screen = c.getInt(screenIndex);
+                                folderInfo.cellX = c.getInt(cellXIndex);
+                                folderInfo.cellY = c.getInt(cellYIndex);
 
                                 // check & update map of what's occupied
-                                if (!checkItemPlacement(occupied, info)) {
+                                if (!checkItemPlacement(occupied, folderInfo)) {
                                     break;
                                 }
-
                                 switch (container) {
-                                case LauncherSettings.Favorites.CONTAINER_DESKTOP:
-                                case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
-                                    sWorkspaceItems.add(info);
-                                    break;
-                                default:
-                                    // Item is in a user folder
-                                    FolderInfo folderInfo =
-                                            findOrMakeFolder(sFolders, container);
-                                    folderInfo.add(info);
-                                    break;
+                                    case LauncherSettings.Favorites.CONTAINER_DESKTOP:
+                                    case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
+                                        sBgWorkspaceItems.add(folderInfo);
+                                        break;
                                 }
-                                sItemsIdMap.put(info.id, info);
 
-                                // now that we've loaded everthing re-save it with the
-                                // icon in case it disappears somehow.
-                                queueIconToBeChecked(sDbIconCache, 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.
+                                sBgItemsIdMap.put(folderInfo.id, folderInfo);
+                                sBgFolders.put(folderInfo.id, folderInfo);
+                                break;
+
+                            case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+                                // Read all Launcher-specific widget details
+                                int appWidgetId = c.getInt(appWidgetIdIndex);
                                 id = c.getLong(idIndex);
-                                Log.e(TAG, "Error loading shortcut " + id + ", removing it");
-                                contentResolver.delete(LauncherSettings.Favorites.getContentUri(
-                                            id, false), null, null);
-                            }
-                            break;
 
-                        case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
-                            id = c.getLong(idIndex);
-                            FolderInfo folderInfo = findOrMakeFolder(sFolders, id);
+                                final AppWidgetProviderInfo provider =
+                                        widgets.getAppWidgetInfo(appWidgetId);
 
-                            folderInfo.title = c.getString(titleIndex);
-                            folderInfo.id = id;
-                            container = c.getInt(containerIndex);
-                            folderInfo.container = container;
-                            folderInfo.screen = c.getInt(screenIndex);
-                            folderInfo.cellX = c.getInt(cellXIndex);
-                            folderInfo.cellY = c.getInt(cellYIndex);
+                                if (!isSafeMode && (provider == null || provider.provider == null ||
+                                        provider.provider.getPackageName() == null)) {
+                                    String log = "Deleting widget that isn't installed anymore: id="
+                                        + id + " appWidgetId=" + appWidgetId;
+                                    Log.e(TAG, log);
+                                    Launcher.sDumpLogs.add(log);
+                                    itemsToRemove.add(id);
+                                } else {
+                                    appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
+                                            provider.provider);
+                                    appWidgetInfo.id = id;
+                                    appWidgetInfo.screen = c.getInt(screenIndex);
+                                    appWidgetInfo.cellX = c.getInt(cellXIndex);
+                                    appWidgetInfo.cellY = c.getInt(cellYIndex);
+                                    appWidgetInfo.spanX = c.getInt(spanXIndex);
+                                    appWidgetInfo.spanY = c.getInt(spanYIndex);
+                                    int[] minSpan = Launcher.getMinSpanForWidget(context, provider);
+                                    appWidgetInfo.minSpanX = minSpan[0];
+                                    appWidgetInfo.minSpanY = minSpan[1];
 
-                            // check & update map of what's occupied
-                            if (!checkItemPlacement(occupied, folderInfo)) {
+                                    container = c.getInt(containerIndex);
+                                    if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
+                                        container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+                                        Log.e(TAG, "Widget found where container != " +
+                                            "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
+                                        continue;
+                                    }
+                                    appWidgetInfo.container = c.getInt(containerIndex);
+
+                                    // check & update map of what's occupied
+                                    if (!checkItemPlacement(occupied, appWidgetInfo)) {
+                                        break;
+                                    }
+                                    sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
+                                    sBgAppWidgets.add(appWidgetInfo);
+                                }
                                 break;
                             }
-                            switch (container) {
-                                case LauncherSettings.Favorites.CONTAINER_DESKTOP:
-                                case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
-                                    sWorkspaceItems.add(folderInfo);
-                                    break;
-                            }
-
-                            sItemsIdMap.put(folderInfo.id, folderInfo);
-                            sFolders.put(folderInfo.id, folderInfo);
-                            break;
-
-                        case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
-                            // Read all Launcher-specific widget details
-                            int appWidgetId = c.getInt(appWidgetIdIndex);
-                            id = c.getLong(idIndex);
-
-                            final AppWidgetProviderInfo provider =
-                                    widgets.getAppWidgetInfo(appWidgetId);
-
-                            if (!isSafeMode && (provider == null || provider.provider == null ||
-                                    provider.provider.getPackageName() == null)) {
-                                String log = "Deleting widget that isn't installed anymore: id="
-                                    + id + " appWidgetId=" + appWidgetId;
-                                Log.e(TAG, log); 
-                                Launcher.sDumpLogs.add(log);
-                                itemsToRemove.add(id);
-                            } else {
-                                appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
-                                        provider.provider);
-                                appWidgetInfo.id = id;
-                                appWidgetInfo.screen = c.getInt(screenIndex);
-                                appWidgetInfo.cellX = c.getInt(cellXIndex);
-                                appWidgetInfo.cellY = c.getInt(cellYIndex);
-                                appWidgetInfo.spanX = c.getInt(spanXIndex);
-                                appWidgetInfo.spanY = c.getInt(spanYIndex);
-                                int[] minSpan = Launcher.getMinSpanForWidget(context, provider);
-                                appWidgetInfo.minSpanX = minSpan[0];
-                                appWidgetInfo.minSpanY = minSpan[1];
-
-                                container = c.getInt(containerIndex);
-                                if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
-                                    container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
-                                    Log.e(TAG, "Widget found where container "
-                                        + "!= CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
-                                    continue;
-                                }
-                                appWidgetInfo.container = c.getInt(containerIndex);
-
-                                // check & update map of what's occupied
-                                if (!checkItemPlacement(occupied, appWidgetInfo)) {
-                                    break;
-                                }
-                                sItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
-                                sAppWidgets.add(appWidgetInfo);
-                            }
-                            break;
+                        } catch (Exception e) {
+                            Log.w(TAG, "Desktop items loading interrupted:", e);
                         }
-                    } catch (Exception e) {
-                        Log.w(TAG, "Desktop items loading interrupted:", e);
+                    }
+                } finally {
+                    c.close();
+                }
+
+                if (itemsToRemove.size() > 0) {
+                    ContentProviderClient client = contentResolver.acquireContentProviderClient(
+                                    LauncherSettings.Favorites.CONTENT_URI);
+                    // Remove dead items
+                    for (long id : itemsToRemove) {
+                        if (DEBUG_LOADERS) {
+                            Log.d(TAG, "Removed id = " + id);
+                        }
+                        // Don't notify content observers
+                        try {
+                            client.delete(LauncherSettings.Favorites.getContentUri(id, false),
+                                    null, null);
+                        } catch (RemoteException e) {
+                            Log.w(TAG, "Could not remove id = " + id);
+                        }
                     }
                 }
-            } finally {
-                c.close();
-            }
 
-            if (itemsToRemove.size() > 0) {
-                ContentProviderClient client = contentResolver.acquireContentProviderClient(
-                                LauncherSettings.Favorites.CONTENT_URI);
-                // Remove dead items
-                for (long id : itemsToRemove) {
-                    if (DEBUG_LOADERS) {
-                        Log.d(TAG, "Removed id = " + id);
-                    }
-                    // Don't notify content observers
-                    try {
-                        client.delete(LauncherSettings.Favorites.getContentUri(id, false),
-                                null, null);
-                    } catch (RemoteException e) {
-                        Log.w(TAG, "Could not remove id = " + id);
-                    }
-                }
-            }
-
-            if (DEBUG_LOADERS) {
-                Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms");
-                Log.d(TAG, "workspace layout: ");
-                for (int y = 0; y < mCellCountY; y++) {
-                    String line = "";
-                    for (int s = 0; s < Launcher.SCREEN_COUNT; s++) {
-                        if (s > 0) {
-                            line += " | ";
+                if (DEBUG_LOADERS) {
+                    Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms");
+                    Log.d(TAG, "workspace layout: ");
+                    for (int y = 0; y < mCellCountY; y++) {
+                        String line = "";
+                        for (int s = 0; s < Launcher.SCREEN_COUNT; s++) {
+                            if (s > 0) {
+                                line += " | ";
+                            }
+                            for (int x = 0; x < mCellCountX; x++) {
+                                line += ((occupied[s][x][y] != null) ? "#" : ".");
+                            }
                         }
-                        for (int x = 0; x < mCellCountX; x++) {
-                            line += ((occupied[s][x][y] != null) ? "#" : ".");
-                        }
+                        Log.d(TAG, "[ " + line + " ]");
                     }
-                    Log.d(TAG, "[ " + line + " ]");
                 }
             }
         }
 
-        /**
-         * Read everything out of our database.
-         */
-        private void bindWorkspace() {
-            final long t = SystemClock.uptimeMillis();
-
-            // Don't use these two variables in any of the callback runnables.
-            // Otherwise we hold a reference to them.
-            final Callbacks oldCallbacks = mCallbacks.get();
-            if (oldCallbacks == null) {
-                // This launcher has exited and nobody bothered to tell us.  Just bail.
-                Log.w(TAG, "LoaderTask running with no launcher");
-                return;
+        /** Filters the set of items who are directly or indirectly (via another container) on the
+         * specified screen. */
+        private void filterCurrentWorkspaceItems(int currentScreen,
+                ArrayList<ItemInfo> allWorkspaceItems,
+                ArrayList<ItemInfo> currentScreenItems,
+                ArrayList<ItemInfo> otherScreenItems) {
+            // Purge any null ItemInfos
+            Iterator<ItemInfo> iter = allWorkspaceItems.iterator();
+            while (iter.hasNext()) {
+                ItemInfo i = iter.next();
+                if (i == null) {
+                    iter.remove();
+                }
             }
 
-            // Get the list of workspace items to load and unbind the existing ShortcutInfos
-            // before we call startBinding() below.
-            final int currentScreen = oldCallbacks.getCurrentWorkspaceScreen();
-            final ArrayList<ItemInfo> tmpWorkspaceItems = unbindWorkspaceItemsOnMainThread();
-            // Order the items for loading as follows: current workspace, hotseat, everything else
-            Collections.sort(tmpWorkspaceItems, new Comparator<ItemInfo>() {
+            // If we aren't filtering on a screen, then the set of items to load is the full set of
+            // items given.
+            if (currentScreen < 0) {
+                currentScreenItems.addAll(allWorkspaceItems);
+            }
+
+            // Order the set of items by their containers first, this allows use to walk through the
+            // list sequentially, build up a list of containers that are in the specified screen,
+            // as well as all items in those containers.
+            Set<Long> itemsOnScreen = new HashSet<Long>();
+            Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() {
+                @Override
+                public int compare(ItemInfo lhs, ItemInfo rhs) {
+                    return (int) (lhs.container - rhs.container);
+                }
+            });
+            for (ItemInfo info : allWorkspaceItems) {
+                if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+                    if (info.screen == currentScreen) {
+                        currentScreenItems.add(info);
+                        itemsOnScreen.add(info.id);
+                    } else {
+                        otherScreenItems.add(info);
+                    }
+                } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+                    currentScreenItems.add(info);
+                    itemsOnScreen.add(info.id);
+                } else {
+                    if (itemsOnScreen.contains(info.container)) {
+                        currentScreenItems.add(info);
+                        itemsOnScreen.add(info.id);
+                    } else {
+                        otherScreenItems.add(info);
+                    }
+                }
+            }
+        }
+
+        /** Filters the set of widgets which are on the specified screen. */
+        private void filterCurrentAppWidgets(int currentScreen,
+                ArrayList<LauncherAppWidgetInfo> appWidgets,
+                ArrayList<LauncherAppWidgetInfo> currentScreenWidgets,
+                ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) {
+            // If we aren't filtering on a screen, then the set of items to load is the full set of
+            // widgets given.
+            if (currentScreen < 0) {
+                currentScreenWidgets.addAll(appWidgets);
+            }
+
+            for (LauncherAppWidgetInfo widget : appWidgets) {
+                if (widget == null) continue;
+                if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
+                        widget.screen == currentScreen) {
+                    currentScreenWidgets.add(widget);
+                } else {
+                    otherScreenWidgets.add(widget);
+                }
+            }
+        }
+
+        /** Filters the set of folders which are on the specified screen. */
+        private void filterCurrentFolders(int currentScreen,
+                HashMap<Long, ItemInfo> itemsIdMap,
+                HashMap<Long, FolderInfo> folders,
+                HashMap<Long, FolderInfo> currentScreenFolders,
+                HashMap<Long, FolderInfo> otherScreenFolders) {
+            // If we aren't filtering on a screen, then the set of items to load is the full set of
+            // widgets given.
+            if (currentScreen < 0) {
+                currentScreenFolders.putAll(folders);
+            }
+
+            for (long id : folders.keySet()) {
+                ItemInfo info = itemsIdMap.get(id);
+                FolderInfo folder = folders.get(id);
+                if (info == null || folder == null) continue;
+                if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
+                        info.screen == currentScreen) {
+                    currentScreenFolders.put(id, folder);
+                } else {
+                    otherScreenFolders.put(id, folder);
+                }
+            }
+        }
+
+        /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
+         * right) */
+        private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
+            // XXX: review this
+            Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
                 @Override
                 public int compare(ItemInfo lhs, ItemInfo rhs) {
                     int cellCountX = LauncherModel.getCellCountX();
@@ -1293,108 +1479,168 @@
                     return (int) (lr - rr);
                 }
             });
-            // Precondition: the items are ordered by page, screen
-            final ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
-            for (ItemInfo ii : tmpWorkspaceItems) {
-                // Prepend the current items, hotseat items, append everything else
-                if (ii.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
-                        ii.screen == currentScreen) {
-                    workspaceItems.add(0, ii);
-                } else if (ii.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
-                    workspaceItems.add(0, ii);
-                } else {
-                    workspaceItems.add(ii);
-                }
-            }
+        }
 
-            // Tell the workspace that we're about to start firing items at it
-            mHandler.post(new Runnable() {
-                public void run() {
-                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
-                    if (callbacks != null) {
-                        callbacks.startBinding();
-                    }
-                }
-            });
+        private void bindWorkspaceItems(final Callbacks oldCallbacks,
+                final ArrayList<ItemInfo> workspaceItems,
+                final ArrayList<LauncherAppWidgetInfo> appWidgets,
+                final HashMap<Long, FolderInfo> folders,
+                ArrayList<Runnable> deferredBindRunnables) {
 
-            // Add the items to the workspace.
+            final boolean postOnMainThread = (deferredBindRunnables != null);
+
+            // Bind the workspace items
             int N = workspaceItems.size();
-            for (int i=0; i<N; i+=ITEMS_CHUNK) {
+            for (int i = 0; i < N; i += ITEMS_CHUNK) {
                 final int start = i;
                 final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
-                mHandler.post(new Runnable() {
+                final Runnable r = new Runnable() {
+                    @Override
                     public void run() {
                         Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                         if (callbacks != null) {
                             callbacks.bindItems(workspaceItems, start, start+chunkSize);
                         }
                     }
-                });
+                };
+                if (postOnMainThread) {
+                    deferredBindRunnables.add(r);
+                } else {
+                    runOnMainThread(r);
+                }
             }
-            // Ensure that we don't use the same folders data structure on the main thread
-            final HashMap<Long, FolderInfo> folders = new HashMap<Long, FolderInfo>(sFolders);
-            mHandler.post(new Runnable() {
+
+            // Bind the folders
+            if (!folders.isEmpty()) {
+                final Runnable r = new Runnable() {
+                    public void run() {
+                        Callbacks callbacks = tryGetCallbacks(oldCallbacks);
+                        if (callbacks != null) {
+                            callbacks.bindFolders(folders);
+                        }
+                    }
+                };
+                if (postOnMainThread) {
+                    deferredBindRunnables.add(r);
+                } else {
+                    runOnMainThread(r);
+                }
+            }
+
+            // Bind the widgets, one at a time
+            N = appWidgets.size();
+            for (int i = 0; i < N; i++) {
+                final LauncherAppWidgetInfo widget = appWidgets.get(i);
+                final Runnable r = new Runnable() {
+                    public void run() {
+                        Callbacks callbacks = tryGetCallbacks(oldCallbacks);
+                        if (callbacks != null) {
+                            callbacks.bindAppWidget(widget);
+                        }
+                    }
+                };
+                if (postOnMainThread) {
+                    deferredBindRunnables.add(r);
+                } else {
+                    runOnMainThread(r);
+                }
+            }
+        }
+
+        /**
+         * Binds all loaded data to actual views on the main thread.
+         */
+        private void bindWorkspace(int synchronizeBindPage) {
+            final long t = SystemClock.uptimeMillis();
+            Runnable r;
+
+            // Don't use these two variables in any of the callback runnables.
+            // Otherwise we hold a reference to them.
+            final Callbacks oldCallbacks = mCallbacks.get();
+            if (oldCallbacks == null) {
+                // This launcher has exited and nobody bothered to tell us.  Just bail.
+                Log.w(TAG, "LoaderTask running with no launcher");
+                return;
+            }
+
+            final boolean isLoadingSynchronously = (synchronizeBindPage > -1);
+            final int currentScreen = isLoadingSynchronously ? synchronizeBindPage :
+                oldCallbacks.getCurrentWorkspaceScreen();
+
+            // Load all the items that are on the current page first (and in the process, unbind
+            // all the existing workspace items before we call startBinding() below.
+            unbindWorkspaceItemsOnMainThread();
+            ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
+            ArrayList<LauncherAppWidgetInfo> appWidgets =
+                    new ArrayList<LauncherAppWidgetInfo>();
+            HashMap<Long, FolderInfo> folders = new HashMap<Long, FolderInfo>();
+            HashMap<Long, ItemInfo> itemsIdMap = new HashMap<Long, ItemInfo>();
+            synchronized (sBgLock) {
+                workspaceItems.addAll(sBgWorkspaceItems);
+                appWidgets.addAll(sBgAppWidgets);
+                folders.putAll(sBgFolders);
+                itemsIdMap.putAll(sBgItemsIdMap);
+            }
+
+            ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();
+            ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();
+            ArrayList<LauncherAppWidgetInfo> currentAppWidgets =
+                    new ArrayList<LauncherAppWidgetInfo>();
+            ArrayList<LauncherAppWidgetInfo> otherAppWidgets =
+                    new ArrayList<LauncherAppWidgetInfo>();
+            HashMap<Long, FolderInfo> currentFolders = new HashMap<Long, FolderInfo>();
+            HashMap<Long, FolderInfo> otherFolders = new HashMap<Long, FolderInfo>();
+
+            // Separate the items that are on the current screen, and all the other remaining items
+            filterCurrentWorkspaceItems(currentScreen, workspaceItems, currentWorkspaceItems,
+                    otherWorkspaceItems);
+            filterCurrentAppWidgets(currentScreen, appWidgets, currentAppWidgets,
+                    otherAppWidgets);
+            filterCurrentFolders(currentScreen, itemsIdMap, folders, currentFolders,
+                    otherFolders);
+            sortWorkspaceItemsSpatially(currentWorkspaceItems);
+            sortWorkspaceItemsSpatially(otherWorkspaceItems);
+
+            // Tell the workspace that we're about to start binding items
+            r = new Runnable() {
                 public void run() {
                     Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                     if (callbacks != null) {
-                        callbacks.bindFolders(folders);
+                        callbacks.startBinding();
                     }
                 }
-            });
-            // Wait until the queue goes empty.
-            mHandler.post(new Runnable() {
-                public void run() {
-                    if (DEBUG_LOADERS) {
-                        Log.d(TAG, "Going to start binding widgets soon.");
+            };
+            runOnMainThread(r);
+
+            // Load items on the current page
+            bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets,
+                    currentFolders, null);
+            if (isLoadingSynchronously) {
+                r = new Runnable() {
+                    public void run() {
+                        Callbacks callbacks = tryGetCallbacks(oldCallbacks);
+                        if (callbacks != null) {
+                            callbacks.onPageBoundSynchronously(currentScreen);
+                        }
                     }
-                }
-            });
-            // Bind the widgets, one at a time.
-            // WARNING: this is calling into the workspace from the background thread,
-            // but since getCurrentScreen() just returns the int, we should be okay.  This
-            // is just a hint for the order, and if it's wrong, we'll be okay.
-            // TODO: instead, we should have that push the current screen into here.
-            N = sAppWidgets.size();
-            // once for the current screen
-            for (int i=0; i<N; i++) {
-                final LauncherAppWidgetInfo widget = sAppWidgets.get(i);
-                if (widget.screen == currentScreen) {
-                    mHandler.post(new Runnable() {
-                        public void run() {
-                            Callbacks callbacks = tryGetCallbacks(oldCallbacks);
-                            if (callbacks != null) {
-                                callbacks.bindAppWidget(widget);
-                            }
-                        }
-                    });
-                }
+                };
+                runOnMainThread(r);
             }
-            // once for the other screens
-            for (int i=0; i<N; i++) {
-                final LauncherAppWidgetInfo widget = sAppWidgets.get(i);
-                if (widget.screen != currentScreen) {
-                    mHandler.post(new Runnable() {
-                        public void run() {
-                            Callbacks callbacks = tryGetCallbacks(oldCallbacks);
-                            if (callbacks != null) {
-                                callbacks.bindAppWidget(widget);
-                            }
-                        }
-                    });
-                }
-            }
-            // Tell the workspace that we're done.
-            mHandler.post(new Runnable() {
+
+            // Load all the remaining pages (if we are loading synchronously, we want to defer this
+            // work until after the first render)
+            mDeferredBindRunnables.clear();
+            bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders,
+                    (isLoadingSynchronously ? mDeferredBindRunnables : null));
+
+            // Tell the workspace that we're done binding items
+            r = new Runnable() {
                 public void run() {
                     Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                     if (callbacks != null) {
                         callbacks.finishBindingItems();
                     }
-                }
-            });
-            // Cleanup
-            mHandler.post(new Runnable() {
-                public void run() {
+
                     // If we're profiling, ensure this is the last thing in the queue.
                     if (DEBUG_LOADERS) {
                         Log.d(TAG, "bound workspace in "
@@ -1403,7 +1649,12 @@
 
                     mIsLoadingAndBindingWorkspace = false;
                 }
-            });
+            };
+            if (isLoadingSynchronously) {
+                mDeferredBindRunnables.add(r);
+            } else {
+                runOnMainThread(r);
+            }
         }
 
         private void loadAndBindAllApps() {
@@ -1435,7 +1686,7 @@
             @SuppressWarnings("unchecked")
             final ArrayList<ApplicationInfo> list
                     = (ArrayList<ApplicationInfo>) mAllAppsList.data.clone();
-            mHandler.post(new Runnable() {
+            Runnable r = new Runnable() {
                 public void run() {
                     final long t = SystemClock.uptimeMillis();
                     final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
@@ -1447,8 +1698,13 @@
                                 + (SystemClock.uptimeMillis()-t) + "ms");
                     }
                 }
-            });
-
+            };
+            boolean isRunningOnMainThread = !(sWorkerThread.getThreadId() == Process.myTid());
+            if (oldCallbacks.isAllAppsVisible() && isRunningOnMainThread) {
+                r.run();
+            } else {
+                mHandler.post(r);
+            }
         }
 
         private void loadAllAppsByBatch() {
@@ -1566,12 +1822,13 @@
         }
 
         public void dumpState() {
-            Log.d(TAG, "mLoaderTask.mContext=" + mContext);
-            Log.d(TAG, "mLoaderTask.mWaitThread=" + mWaitThread);
-            Log.d(TAG, "mLoaderTask.mIsLaunching=" + mIsLaunching);
-            Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
-            Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
-            Log.d(TAG, "mItems size=" + sWorkspaceItems.size());
+            synchronized (sBgLock) {
+                Log.d(TAG, "mLoaderTask.mContext=" + mContext);
+                Log.d(TAG, "mLoaderTask.mIsLaunching=" + mIsLaunching);
+                Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
+                Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
+                Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size());
+            }
         }
     }
 
@@ -1702,11 +1959,13 @@
      */
     ArrayList<ShortcutInfo> getShortcutInfosForPackage(String packageName) {
         ArrayList<ShortcutInfo> infos = new ArrayList<ShortcutInfo>();
-        for (ItemInfo i : sWorkspaceItems) {
-            if (i instanceof ShortcutInfo) {
-                ShortcutInfo info = (ShortcutInfo) i;
-                if (packageName.equals(info.getPackageName())) {
-                    infos.add(info);
+        synchronized (sBgLock) {
+            for (ItemInfo i : sBgWorkspaceItems) {
+                if (i instanceof ShortcutInfo) {
+                    ShortcutInfo info = (ShortcutInfo) i;
+                    if (packageName.equals(info.getPackageName())) {
+                        infos.add(info);
+                    }
                 }
             }
         }
diff --git a/src/com/android/launcher2/PagedViewWidget.java b/src/com/android/launcher2/PagedViewWidget.java
index 66b7080..b804ab0 100644
--- a/src/com/android/launcher2/PagedViewWidget.java
+++ b/src/com/android/launcher2/PagedViewWidget.java
@@ -21,6 +21,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
+import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
@@ -44,6 +45,7 @@
     boolean mShortPressTriggered = false;
     static PagedViewWidget sShortpressTarget = null;
     boolean mIsAppWidget;
+    private final Rect mOriginalImagePadding = new Rect();
 
     public PagedViewWidget(Context context) {
         this(context, null);
@@ -63,6 +65,17 @@
         setClipToPadding(false);
     }
 
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        final ImageView image = (ImageView) findViewById(R.id.widget_preview);
+        mOriginalImagePadding.left = image.getPaddingLeft();
+        mOriginalImagePadding.top = image.getPaddingTop();
+        mOriginalImagePadding.right = image.getPaddingRight();
+        mOriginalImagePadding.bottom = image.getPaddingBottom();
+    }
+
     public static void setDeletePreviewsWhenDetachedFromWindow(boolean value) {
         sDeletePreviewsWhenDetachedFromWindow = value;
     }
@@ -79,7 +92,7 @@
                     preview.getBitmap().recycle();
                 }
                 image.setImageDrawable(null);
-                }
+            }
         }
     }
 
@@ -117,8 +130,8 @@
     public int[] getPreviewSize() {
         final ImageView i = (ImageView) findViewById(R.id.widget_preview);
         int[] maxSize = new int[2];
-        maxSize[0] = i.getWidth() - i.getPaddingLeft() - i.getPaddingRight();
-        maxSize[1] = i.getHeight() - i.getPaddingTop();
+        maxSize[0] = i.getWidth() - mOriginalImagePadding.left - mOriginalImagePadding.right;
+        maxSize[1] = i.getHeight() - mOriginalImagePadding.top;
         return maxSize;
     }
 
@@ -132,10 +145,10 @@
                 // center horizontally
                 int[] imageSize = getPreviewSize();
                 int centerAmount = (imageSize[0] - preview.getIntrinsicWidth()) / 2;
-                image.setPadding(image.getPaddingLeft() + centerAmount,
-                        image.getPaddingTop(),
-                        image.getPaddingRight(),
-                        image.getPaddingBottom());
+                image.setPadding(mOriginalImagePadding.left + centerAmount,
+                        mOriginalImagePadding.top,
+                        mOriginalImagePadding.right,
+                        mOriginalImagePadding.bottom);
             }
             image.setAlpha(1f);
             image.mAllowRequestLayout = true;
diff --git a/src/com/android/launcher2/PagedViewWithDraggableItems.java b/src/com/android/launcher2/PagedViewWithDraggableItems.java
index 22fd82b..9cdd74f 100644
--- a/src/com/android/launcher2/PagedViewWithDraggableItems.java
+++ b/src/com/android/launcher2/PagedViewWithDraggableItems.java
@@ -105,7 +105,7 @@
         // Return early if we are still animating the pages
         if (mNextPage != INVALID_PAGE) return false;
         // When we have exited all apps or are in transition, disregard long clicks
-        if (!mLauncher.isAllAppsCustomizeOpen() ||
+        if (!mLauncher.isAllAppsVisible() ||
                 mLauncher.getWorkspace().isSwitchingState()) return false;
         // Return if global dragging is not enabled
         if (!mLauncher.isDraggingEnabled()) return false;
diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java
index 2d2340a..44b9f68 100644
--- a/src/com/android/launcher2/Workspace.java
+++ b/src/com/android/launcher2/Workspace.java
@@ -47,6 +47,7 @@
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.Log;
+import android.util.SparseArray;
 import android.view.Display;
 import android.view.MotionEvent;
 import android.view.View;
@@ -232,6 +233,9 @@
     private int mLastReorderX = -1;
     private int mLastReorderY = -1;
 
+    private SparseArray<Parcelable> mSavedStates;
+    private final ArrayList<Integer> mRestoredPages = new ArrayList<Integer>();
+
     // These variables are used for storing the initial and final values during workspace animations
     private int mSavedScrollX;
     private float mSavedRotationY;
@@ -1329,6 +1333,14 @@
         }
 
         super.onDraw(canvas);
+
+        // Call back to LauncherModel to finish binding after the first draw
+        post(new Runnable() {
+            @Override
+            public void run() {
+                mLauncher.getModel().bindRemainingSynchronousPages();
+            }
+        });
     }
 
     boolean isDrawingBackgroundGradient() {
@@ -3403,6 +3415,32 @@
     }
 
     @Override
+    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
+        // We don't dispatch restoreInstanceState to our children using this code path.
+        // Some pages will be restored immediately as their items are bound immediately, and 
+        // others we will need to wait until after their items are bound.
+        mSavedStates = container;
+    }
+
+    public void restoreInstanceStateForChild(int child) {
+        if (mSavedStates != null) {
+            mRestoredPages.add(child);
+            CellLayout cl = (CellLayout) getChildAt(child);
+            cl.restoreInstanceState(mSavedStates);
+        }
+    }
+
+    public void restoreInstanceStateForRemainingPages() {
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            if (!mRestoredPages.contains(i)) {
+                restoreInstanceStateForChild(i);
+            }
+        }
+        mRestoredPages.clear();
+    }
+
+    @Override
     public void scrollLeft() {
         if (!isSmall() && !mIsSwitchingState) {
             super.scrollLeft();