merge in jb-release history after reset to master
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index f4cece0..1481540 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -91,7 +91,7 @@
     <string name="workspace_cling_title" msgid="738396473989890567">"Fühlen Sie sich wie zu Hause"</string>
     <string name="workspace_cling_move_item" msgid="791013895761065070">"Hier können Sie Ihre Lieblings-Apps ablegen."</string>
     <string name="workspace_cling_open_all_apps" msgid="2459977609848572588">"Berühren Sie den Kreis für eine Übersicht aller Apps."</string>
-    <string name="all_apps_cling_title" msgid="2559734712581447107">"Einige Apps auswählen"</string>
+    <string name="all_apps_cling_title" msgid="2559734712581447107">"Apps auswählen"</string>
     <string name="all_apps_cling_add_item" msgid="5665035103260318891">"Berühren und halten Sie eine App, um sie zum Startbildschirm hinzuzufügen."</string>
     <string name="folder_cling_title" msgid="4308949882377840953">"Apps mit Ordnern organisieren"</string>
     <string name="folder_cling_move_item" msgid="270598675060435169">"Berühren und halten Sie eine App, um sie zu verschieben."</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 10dcdf2..dd93a57 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -32,7 +32,7 @@
     <string name="external_drop_widget_error" msgid="2285187188524172774">"Tidak dapat melepas item ke layar Utama ini."</string>
     <string name="external_drop_widget_pick_title" msgid="7040647073452295370">"Pilih widget untuk membuat"</string>
     <string name="rename_folder_label" msgid="5646236631298452787">"Nama folder"</string>
-    <string name="rename_folder_title" msgid="4544573104191526550">"Ubah nama folder"</string>
+    <string name="rename_folder_title" msgid="4544573104191526550">"Ganti nama folder"</string>
     <string name="rename_action" msgid="6016003384693240896">"OK"</string>
     <string name="cancel_action" msgid="3811860427489435048">"Batal"</string>
     <string name="menu_item_add_item" msgid="6233177331075781114">"Tambahkan ke Layar Utama"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 81525cf..ad38b9b 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -20,12 +20,12 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="application_name" msgid="8424725141379931883">"启动器"</string>
-    <string name="uid_name" msgid="3371120195364560632">"Android 核心应用程序"</string>
+    <string name="uid_name" msgid="3371120195364560632">"Android 核心应用"</string>
     <string name="folder_name" msgid="8551881338202938211"></string>
     <string name="chooser_wallpaper" msgid="6063168087625352235">"选择壁纸来源"</string>
     <string name="wallpaper_instructions" msgid="4215640646180727542">"设置壁纸"</string>
     <string name="pick_wallpaper" msgid="5630222540525626723">"壁纸"</string>
-    <string name="activity_not_found" msgid="217823393239365967">"未安装该应用程序。"</string>
+    <string name="activity_not_found" msgid="217823393239365967">"未安装该应用。"</string>
     <string name="widgets_tab_label" msgid="9145860100000983599">"窗口小部件"</string>
     <string name="long_press_widget_to_add" msgid="7395697462851217506">"触摸并按住可选取窗口小部件。"</string>
     <string name="market" msgid="2652226429823445833">"购买"</string>
@@ -36,7 +36,7 @@
     <string name="rename_action" msgid="6016003384693240896">"确定"</string>
     <string name="cancel_action" msgid="3811860427489435048">"取消"</string>
     <string name="menu_item_add_item" msgid="6233177331075781114">"添加到主屏幕"</string>
-    <string name="group_applications" msgid="2103752818818161976">"应用程序"</string>
+    <string name="group_applications" msgid="2103752818818161976">"应用"</string>
     <string name="group_shortcuts" msgid="9133529424900391877">"快捷方式"</string>
     <string name="group_widgets" msgid="6704978494073105844">"窗口小部件"</string>
     <string name="group_wallpapers" msgid="1568191644272224858">"壁纸"</string>
@@ -46,56 +46,56 @@
     <string name="shortcut_uninstalled" msgid="2129499669449749995">"已删除“<xliff:g id="NAME">%s</xliff:g>”快捷方式。"</string>
     <string name="shortcut_duplicate" msgid="4757756326465060694">"“<xliff:g id="NAME">%s</xliff:g>”快捷方式已存在。"</string>
     <string name="title_select_shortcut" msgid="1873670208166882222">"选择快捷方式"</string>
-    <string name="title_select_application" msgid="1793455815754848652">"选择应用程序"</string>
-    <string name="all_apps_button_label" msgid="2578400570124163469">"应用程序"</string>
+    <string name="title_select_application" msgid="1793455815754848652">"选择应用"</string>
+    <string name="all_apps_button_label" msgid="2578400570124163469">"应用"</string>
     <string name="all_apps_home_button_label" msgid="1022222300329398558">"主屏幕"</string>
     <string name="delete_zone_label_workspace" msgid="7153615831493049150">"删除"</string>
     <string name="delete_zone_label_all_apps" msgid="6664588234817475108">"卸载"</string>
     <string name="delete_target_label" msgid="665300185123139530">"删除"</string>
     <string name="delete_target_uninstall_label" msgid="748894921183769150">"卸载"</string>
-    <string name="info_target_label" msgid="4019495079517426980">"应用程序信息"</string>
+    <string name="info_target_label" msgid="4019495079517426980">"应用信息"</string>
     <string name="accessibility_search_button" msgid="816822994629942611">"搜索"</string>
     <string name="accessibility_voice_search_button" msgid="3938249215065842475">"语音搜索"</string>
-    <string name="accessibility_all_apps_button" msgid="8803738611398979849">"应用程序"</string>
+    <string name="accessibility_all_apps_button" msgid="8803738611398979849">"应用"</string>
     <string name="accessibility_delete_button" msgid="3628162007991023603">"删除"</string>
     <string name="delete_zone_label_all_apps_system_app" msgid="3683920959591819044">"卸载更新"</string>
     <string name="menu_add" msgid="3065046628354640854">"添加"</string>
-    <string name="menu_manage_apps" msgid="2308685199463588895">"管理应用程序"</string>
+    <string name="menu_manage_apps" msgid="2308685199463588895">"管理应用"</string>
     <string name="menu_wallpaper" msgid="5837429080911269832">"壁纸"</string>
     <string name="menu_search" msgid="4826514464423239041">"搜索"</string>
     <string name="menu_notifications" msgid="6424587053194766192">"通知"</string>
     <string name="menu_settings" msgid="3946232973327980394">"系统设置"</string>
     <string name="menu_help" msgid="4901160661634590633">"帮助"</string>
-    <string name="cab_menu_delete_app" msgid="4089398025537640349">"卸载该应用程序"</string>
-    <string name="cab_menu_app_info" msgid="914548323652698884">"应用程序详情"</string>
-    <string name="cab_app_selection_text" msgid="6378522164293415735">"选中了 1 个应用程序"</string>
+    <string name="cab_menu_delete_app" msgid="4089398025537640349">"卸载该应用"</string>
+    <string name="cab_menu_app_info" msgid="914548323652698884">"应用详情"</string>
+    <string name="cab_app_selection_text" msgid="6378522164293415735">"选中了 1 个应用"</string>
     <string name="cab_widget_selection_text" msgid="962527270506951955">"已选中 1 个窗口小部件"</string>
     <string name="cab_folder_selection_text" msgid="8916111874189565067">"已选中 1 个文件夹"</string>
     <string name="cab_shortcut_selection_text" msgid="8115847384500412878">"已选中 1 个快捷方式"</string>
     <string name="permlab_install_shortcut" msgid="1201690825493376489">"安装快捷方式"</string>
-    <string name="permdesc_install_shortcut" msgid="8634424803272077038">"允许应用程序自行添加快捷方式。"</string>
+    <string name="permdesc_install_shortcut" msgid="8634424803272077038">"允许应用自行添加快捷方式。"</string>
     <string name="permlab_uninstall_shortcut" msgid="7696645932555926449">"卸载快捷方式"</string>
-    <string name="permdesc_uninstall_shortcut" msgid="274355570620220977">"允许应用程序自行删除快捷方式,而无需用户干预。"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="274355570620220977">"允许应用自行删除快捷方式,而无需用户干预。"</string>
     <string name="permlab_read_settings" msgid="3452408290738106747">"读取主屏幕的设置和快捷方式"</string>
-    <string name="permdesc_read_settings" msgid="5788109303585403679">"允许应用程序读取主屏幕中的设置和快捷方式。"</string>
+    <string name="permdesc_read_settings" msgid="5788109303585403679">"允许应用读取主屏幕中的设置和快捷方式。"</string>
     <string name="permlab_write_settings" msgid="1360567537236705628">"写入主屏幕的设置和快捷方式"</string>
-    <string name="permdesc_write_settings" msgid="8530105489115785531">"允许应用程序更改主屏幕中的设置和快捷方式。"</string>
+    <string name="permdesc_write_settings" msgid="8530105489115785531">"允许应用更改主屏幕中的设置和快捷方式。"</string>
     <string name="gadget_error_text" msgid="8359351016167075858">"载入窗口小部件时出现问题"</string>
-    <string name="uninstall_system_app_text" msgid="6429814133777046491">"这是系统应用程序,无法卸载。"</string>
+    <string name="uninstall_system_app_text" msgid="6429814133777046491">"这是系统应用,无法卸载。"</string>
     <string name="dream_name" msgid="2847171357608437154">"Rocket Launcher"</string>
     <string name="folder_hint_text" msgid="8633351560105748141">"未命名文件夹"</string>
     <string name="default_scroll_format" msgid="4057140866420001240">"第 %1$d 页,共 %2$d 页"</string>
     <string name="workspace_scroll_format" msgid="7911126267695001437">"工作区:第 %1$d 页,共 %2$d 页"</string>
-    <string name="apps_customize_apps_scroll_format" msgid="5494241912377704885">"应用程序:第 %1$d 页,共 %2$d 页"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="5494241912377704885">"应用:第 %1$d 页,共 %2$d 页"</string>
     <string name="apps_customize_widgets_scroll_format" msgid="5383009742241717437">"窗口小部件:第 %1$d 页,共 %2$d 页"</string>
     <string name="workspace_cling_title" msgid="738396473989890567">"随意浏览"</string>
-    <string name="workspace_cling_move_item" msgid="791013895761065070">"您可以在此处放置自己喜爱的应用程序。"</string>
-    <string name="workspace_cling_open_all_apps" msgid="2459977609848572588">"要查看您的所有应用程序,请触摸该圆圈。"</string>
-    <string name="all_apps_cling_title" msgid="2559734712581447107">"选择一些应用程序"</string>
-    <string name="all_apps_cling_add_item" msgid="5665035103260318891">"要将某个应用程序添加到主屏幕,请触摸并按住该应用程序。"</string>
-    <string name="folder_cling_title" msgid="4308949882377840953">"使用文件夹整理应用程序"</string>
-    <string name="folder_cling_move_item" msgid="270598675060435169">"要移动应用程序,请触摸并按住该应用程序。"</string>
-    <string name="folder_cling_create_folder" msgid="8352867485656129478">"要在主屏幕上创建新文件夹,请将一个应用程序叠放到另一个上。"</string>
+    <string name="workspace_cling_move_item" msgid="791013895761065070">"您可以在此处放置自己喜爱的应用。"</string>
+    <string name="workspace_cling_open_all_apps" msgid="2459977609848572588">"要查看您的所有应用,请触摸该圆圈。"</string>
+    <string name="all_apps_cling_title" msgid="2559734712581447107">"选择一些应用"</string>
+    <string name="all_apps_cling_add_item" msgid="5665035103260318891">"要将某个应用添加到主屏幕,请触摸并按住该应用。"</string>
+    <string name="folder_cling_title" msgid="4308949882377840953">"使用文件夹整理应用"</string>
+    <string name="folder_cling_move_item" msgid="270598675060435169">"要移动应用,请触摸并按住该应用。"</string>
+    <string name="folder_cling_create_folder" msgid="8352867485656129478">"要在主屏幕上创建新文件夹,请将一个应用叠放到另一个上。"</string>
     <string name="cling_dismiss" msgid="2780907108735868381">"确定"</string>
     <string name="folder_opened" msgid="1262064100943801533">"文件夹已打开,尺寸为 <xliff:g id="WIDTH">%1$d</xliff:g> × <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="1335478160661137579">"触摸可关闭文件夹"</string>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index a9b14d0..509a670 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -82,7 +82,7 @@
     <!-- Options in "Add to Home" dialog box; Title of the group containing the list of apps that can set the wallpaper-->
     <string name="group_wallpapers">Wallpapers</string>
     <!-- Error message when user has filled a home screen, possibly not used -->
-    <string name="out_of_space">No more room on this Home screen.</string>
+    <string name="out_of_space">No more room on your Home screens.</string>
     <!-- Error message when user tries to drop an invalid item on the hotseat -->
     <string name="invalid_hotseat_item">This widget is too large for the hotseat.</string>
     <!-- Message displayed when a shortcut is created by an external application -->
diff --git a/src/com/android/launcher2/AppsCustomizePagedView.java b/src/com/android/launcher2/AppsCustomizePagedView.java
index a7ff309..039ee8a 100644
--- a/src/com/android/launcher2/AppsCustomizePagedView.java
+++ b/src/com/android/launcher2/AppsCustomizePagedView.java
@@ -32,7 +32,6 @@
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
-import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
 import android.graphics.MaskFilter;
 import android.graphics.Matrix;
@@ -170,7 +169,8 @@
  */
 public class AppsCustomizePagedView extends PagedViewWithDraggableItems implements
         AllAppsView, View.OnClickListener, View.OnKeyListener, DragSource,
-        PagedViewIcon.PressedCallback, PagedViewWidget.ShortPressListener {
+        PagedViewIcon.PressedCallback, PagedViewWidget.ShortPressListener,
+        LauncherTransitionable {
     static final String LOG_TAG = "AppsCustomizePagedView";
 
     /**
@@ -241,6 +241,11 @@
     PendingAddWidgetInfo mCreateWidgetInfo = null;
     private boolean mDraggingWidget = false;
 
+    // Deferral of loading widget previews during launcher transitions
+    private boolean mInTransition;
+    private ArrayList<AsyncTaskPageData> mDeferredSyncWidgetPageItems =
+        new ArrayList<AsyncTaskPageData>();
+
     public AppsCustomizePagedView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mLayoutInflater = LayoutInflater.from(context);
@@ -720,9 +725,14 @@
         return true;
     }
 
-    private void endDragging(View target, boolean success) {
+    /**
+     * Clean up after dragging.
+     *
+     * @param target where the item was dragged to (can be null if the item was flung)
+     */
+    private void endDragging(View target, boolean isFlingToDelete, boolean success) {
         mLauncher.getWorkspace().onDragStopped(success);
-        if (!success || (target != mLauncher.getWorkspace() &&
+        if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
                 !(target instanceof DeleteDropTarget))) {
             // Exit spring loaded mode if we have not successfully dropped or have not handled the
             // drop in Workspace
@@ -732,8 +742,38 @@
     }
 
     @Override
-    public void onDropCompleted(View target, DragObject d, boolean success) {
-        endDragging(target, success);
+    public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
+        mInTransition = true;
+        if (toWorkspace) {
+            cancelAllTasks();
+        }
+    }
+
+    @Override
+    public View getContent() {
+        return null;
+    }
+
+    @Override
+    public void onLauncherTransitionStep(Launcher l, float t) {
+    }
+
+    @Override
+    public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
+        mInTransition = false;
+        for (AsyncTaskPageData d : mDeferredSyncWidgetPageItems) {
+            onSyncWidgetPageItems(d);
+        }
+        mDeferredSyncWidgetPageItems.clear();
+    }
+
+    @Override
+    public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete,
+            boolean success) {
+        // Return early and wait for onFlingToDeleteCompleted if this was the result of a fling
+        if (isFlingToDelete) return;
+
+        endDragging(target, false, success);
 
         // Display an error message if the drag failed due to there not being enough space on the
         // target layout we were dropping on.
@@ -760,8 +800,17 @@
         mDraggingWidget = false;
     }
 
+    @Override
+    public void onFlingToDeleteCompleted() {
+        // We just dismiss the drag when we fling, so cleanup here
+        endDragging(null, true, true);
+        cleanupWidgetPreloading();
+        mDraggingWidget = false;
+    }
+
+    @Override
     public boolean supportsFlingToDelete() {
-        return false;
+        return true;
     }
 
     @Override
@@ -789,6 +838,7 @@
             AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
             task.cancel(false);
             iter.remove();
+            mDirtyPageContent.set(task.page, true);
         }
     }
 
@@ -808,7 +858,7 @@
         Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
         while (iter.hasNext()) {
             AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
-            int pageIndex = task.page + mNumAppsPages;
+            int pageIndex = task.page;
             if ((mNextPage > mCurrentPage && pageIndex >= mCurrentPage) ||
                 (mNextPage < mCurrentPage && pageIndex <= mCurrentPage)) {
                 task.setThreadPriority(getThreadPriorityForPage(pageIndex));
@@ -910,7 +960,7 @@
         int minPageDiff = Integer.MAX_VALUE;
         while (iter.hasNext()) {
             AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
-            minPageDiff = Math.abs(task.page + mNumAppsPages - toPage);
+            minPageDiff = Math.abs(task.page - toPage);
         }
 
         int rawPageDiff = Math.abs(page - toPage);
@@ -945,7 +995,7 @@
         Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
         while (iter.hasNext()) {
             AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
-            int taskPage = task.page + mNumAppsPages;
+            int taskPage = task.page;
             if (taskPage < getAssociatedLowerPageBound(mCurrentPage) ||
                     taskPage > getAssociatedUpperPageBound(mCurrentPage)) {
                 task.cancel(false);
@@ -956,7 +1006,7 @@
         }
 
         // We introduce a slight delay to order the loading of side pages so that we don't thrash
-        final int sleepMs = getSleepForPage(page + mNumAppsPages);
+        final int sleepMs = getSleepForPage(page);
         AsyncTaskPageData pageData = new AsyncTaskPageData(page, widgets, cellWidth, cellHeight,
             new AsyncTaskCallback() {
                 @Override
@@ -976,20 +1026,17 @@
             new AsyncTaskCallback() {
                 @Override
                 public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
-                    try {
-                        mRunningTasks.remove(task);
-                        if (task.isCancelled()) return;
-                        onSyncWidgetPageItems(data);
-                    } finally {
-                        data.cleanup(task.isCancelled());
-                    }
+                    mRunningTasks.remove(task);
+                    if (task.isCancelled()) return;
+                    // do cleanup inside onSyncWidgetPageItems
+                    onSyncWidgetPageItems(data);
                 }
             });
 
         // Ensure that the task is appropriately prioritized and runs in parallel
         AppsCustomizeAsyncTask t = new AppsCustomizeAsyncTask(page,
                 AsyncTaskPageData.Type.LoadWidgetPreviewData);
-        t.setThreadPriority(getThreadPriorityForPage(page + mNumAppsPages));
+        t.setThreadPriority(getThreadPriorityForPage(page));
         t.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, pageData);
         mRunningTasks.add(t);
     }
@@ -1132,13 +1179,13 @@
                 - ((mWidgetCountY - 1) * mWidgetHeightGap)) / mWidgetCountY);
 
         // Prepare the set of widgets to load previews for in the background
-        int offset = page * numItemsPerPage;
+        int offset = (page - mNumAppsPages) * numItemsPerPage;
         for (int i = offset; i < Math.min(offset + numItemsPerPage, mWidgets.size()); ++i) {
             items.add(mWidgets.get(i));
         }
 
         // Prepopulate the pages with the other widget info, and fill in the previews later
-        final PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page + mNumAppsPages);
+        final PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page);
         layout.setColumnCount(layout.getCellCountX());
         for (int i = 0; i < items.size(); ++i) {
             Object rawInfo = items.get(i);
@@ -1249,29 +1296,38 @@
             }
         }
     }
+
     private void onSyncWidgetPageItems(AsyncTaskPageData data) {
-        int page = data.page;
-        PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page + mNumAppsPages);
-
-        ArrayList<Object> items = data.items;
-        int count = items.size();
-        for (int i = 0; i < count; ++i) {
-            PagedViewWidget widget = (PagedViewWidget) layout.getChildAt(i);
-            if (widget != null) {
-                Bitmap preview = data.generatedImages.get(i);
-                widget.applyPreview(new FastBitmapDrawable(preview), i);
-            }
+        if (mInTransition) {
+            mDeferredSyncWidgetPageItems.add(data);
+            return;
         }
+        try {
+            int page = data.page;
+            PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page);
 
-        layout.createHardwareLayer();
-        invalidate();
+            ArrayList<Object> items = data.items;
+            int count = items.size();
+            for (int i = 0; i < count; ++i) {
+                PagedViewWidget widget = (PagedViewWidget) layout.getChildAt(i);
+                if (widget != null) {
+                    Bitmap preview = data.generatedImages.get(i);
+                    widget.applyPreview(new FastBitmapDrawable(preview), i);
+                }
+            }
 
-        // Update all thread priorities
-        Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
-        while (iter.hasNext()) {
-            AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
-            int pageIndex = task.page + mNumAppsPages;
-            task.setThreadPriority(getThreadPriorityForPage(pageIndex));
+            layout.createHardwareLayer();
+            invalidate();
+
+            // Update all thread priorities
+            Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
+            while (iter.hasNext()) {
+                AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
+                int pageIndex = task.page;
+                task.setThreadPriority(getThreadPriorityForPage(pageIndex));
+            }
+        } finally {
+            data.cleanup(false);
         }
     }
 
@@ -1285,7 +1341,7 @@
             PagedViewGridLayout layout = new PagedViewGridLayout(context, mWidgetCountX,
                     mWidgetCountY);
             setupPage(layout);
-            addView(layout, new PagedViewGridLayout.LayoutParams(LayoutParams.MATCH_PARENT,
+            addView(layout, new PagedView.LayoutParams(LayoutParams.MATCH_PARENT,
                     LayoutParams.MATCH_PARENT));
         }
 
@@ -1301,14 +1357,14 @@
         if (page < mNumAppsPages) {
             syncAppsPageItems(page, immediate);
         } else {
-            syncWidgetPageItems(page - mNumAppsPages, immediate);
+            syncWidgetPageItems(page, immediate);
         }
     }
 
     // We want our pages to be z-ordered such that the further a page is to the left, the higher
     // it is in the z-order. This is important to insure touch events are handled correctly.
     View getPageAt(int index) {
-        return getChildAt(getChildCount() - index - 1);
+        return getChildAt(indexToPage(index));
     }
 
     @Override
diff --git a/src/com/android/launcher2/AppsCustomizeTabHost.java b/src/com/android/launcher2/AppsCustomizeTabHost.java
index 7108c9f..af0f205 100644
--- a/src/com/android/launcher2/AppsCustomizeTabHost.java
+++ b/src/com/android/launcher2/AppsCustomizeTabHost.java
@@ -355,6 +355,11 @@
             // force building the layer, so you don't get a blip early in an animation
             // when the layer is created layer
             buildLayer();
+
+            // Let the GC system know that now is a good time to do any garbage
+            // collection; makes it less likely we'll get a GC during the all apps
+            // to workspace animation
+            System.gc();
         }
     }
 
@@ -366,6 +371,7 @@
     /* LauncherTransitionable overrides */
     @Override
     public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
+        mAppsCustomizePane.onLauncherTransitionStart(l, animated, toWorkspace);
         mInTransition = true;
         mTransitioningToWorkspace = toWorkspace;
 
@@ -405,6 +411,7 @@
 
     @Override
     public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
+        mAppsCustomizePane.onLauncherTransitionEnd(l, animated, toWorkspace);
         mInTransition = false;
         if (animated) {
             setLayerType(LAYER_TYPE_NONE, null);
diff --git a/src/com/android/launcher2/CellLayout.java b/src/com/android/launcher2/CellLayout.java
index 5969f98..3b77f35 100644
--- a/src/com/android/launcher2/CellLayout.java
+++ b/src/com/android/launcher2/CellLayout.java
@@ -1101,8 +1101,9 @@
                 @Override
                 public void onAnimationUpdate(ValueAnimator animation) {
                     float r = ((Float) animation.getAnimatedValue()).floatValue();
-                    child.setTranslationX(r * (newX - oldX));
-                    child.setTranslationY(r * (newY - oldY));
+                    lp.x = (int) (r * newX + (1 - r) * oldX);
+                    lp.y = (int) (r * newY + (1 - r) * oldY);
+                    child.requestLayout();
                 }
             });
             va.addListener(new AnimatorListenerAdapter() {
@@ -1112,8 +1113,6 @@
                     // has interrupted this one, and we don't want to lock the item into
                     // place just yet.
                     if (!cancelled) {
-                        child.setTranslationX(0);
-                        child.setTranslationY(0);
                         lp.isLockedToGrid = true;
                         child.requestLayout();
                     }
@@ -1574,31 +1573,28 @@
     }
 
     private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
-            int[] direction) {
-        LayoutParams lp = (LayoutParams) v.getLayoutParams();
+            int[] direction, ItemConfiguration currentState) {
+        CellAndSpan c = currentState.map.get(v);
         boolean success = false;
-        markCellsForView(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan,
-                lp.cellVSpan, mTmpOccupied, false);
+        markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
         markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
 
-        findNearestArea(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan,
-                direction, mTmpOccupied, null, mTempLocation);
+        findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
 
         if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
-            lp.tmpCellX = mTempLocation[0];
-            lp.tmpCellY = mTempLocation[1];
+            c.x = mTempLocation[0];
+            c.y = mTempLocation[1];
             success = true;
 
         }
-        markCellsForView(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan,
-                lp.cellVSpan, mTmpOccupied, true);
+        markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
         return success;
     }
 
     // This method looks in the specified direction to see if there is an additional view
     // immediately adjecent in that direction
     private boolean addViewInDirection(ArrayList<View> views, Rect boundingRect, int[] direction,
-            boolean[][] occupied) {
+            boolean[][] occupied, ItemConfiguration currentState) {
         boolean found = false;
 
         int childCount = mShortcutsAndWidgets.getChildCount();
@@ -1624,16 +1620,17 @@
         for (int i = 0; i < childCount; i++) {
             View child = mShortcutsAndWidgets.getChildAt(i);
             if (views.contains(child)) continue;
-            LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            CellAndSpan c = currentState.map.get(child);
 
-            r1.set(lp.tmpCellX, lp.tmpCellY, lp.tmpCellX + lp.cellHSpan, lp.tmpCellY + lp.cellVSpan);
+            LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
             if (Rect.intersects(r0, r1)) {
                 if (!lp.canReorder) {
                     return false;
                 }
                 boolean pushed = false;
-                for (int x = lp.tmpCellX; x < lp.tmpCellX + lp.cellHSpan; x++) {
-                    for (int y = lp.tmpCellY; y < lp.tmpCellY + lp.cellVSpan; y++) {
+                for (int x = c.x; x < c.x + c.spanX; x++) {
+                    for (int y = c.y; y < c.y + c.spanY; y++) {
                         boolean inBounds = x - deltaX >= 0 && x -deltaX < mCountX
                                 && y - deltaY >= 0 && y - deltaY < mCountY;
                         if (inBounds && occupied[x - deltaX][y - deltaY]) {
@@ -1643,8 +1640,7 @@
                 }
                 if (pushed) {
                     views.add(child);
-                    boundingRect.union(lp.tmpCellX, lp.tmpCellY, lp.tmpCellX + lp.cellHSpan,
-                            lp.tmpCellY + lp.cellVSpan);
+                    boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
                     found = true;
                 }
             }
@@ -1652,116 +1648,72 @@
         return found;
     }
 
-    private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
-            int[] direction) {
-        if (views.size() == 0) return true;
-
-
-        boolean success = false;
-
-        // We construct a rect which represents the entire group of views
-        Rect boundingRect = null;
-        for (View v: views) {
-            LayoutParams lp = (LayoutParams) v.getLayoutParams();
-            if (boundingRect == null) {
-                boundingRect = new Rect(lp.tmpCellX, lp.tmpCellY, lp.tmpCellX + lp.cellHSpan,
-                        lp.tmpCellY + lp.cellVSpan);
-            } else {
-                boundingRect.union(lp.tmpCellX, lp.tmpCellY, lp.tmpCellX + lp.cellHSpan,
-                        lp.tmpCellY + lp.cellVSpan);
-            }
-        }
-
-        ArrayList<View> dup = (ArrayList<View>) views.clone();
-        while (addViewInDirection(dup, boundingRect, direction, mTmpOccupied)) {
-        }
-        for (View v: dup) {
-            LayoutParams lp = (LayoutParams) v.getLayoutParams();
-            markCellsForView(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan,
-                    lp.cellVSpan, mTmpOccupied, false); 
-        }
-
-        boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
-        int top = boundingRect.top;
-        int left = boundingRect.left;
-        for (View v: dup) {
-            LayoutParams lp = (LayoutParams) v.getLayoutParams();
-            markCellsForView(lp.tmpCellX - left, lp.tmpCellY - top, lp.cellHSpan,
-                    lp.cellVSpan, blockOccupied, true); 
-        }
-
-        markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
-
-        findNearestAreaInDirection(boundingRect.left, boundingRect.top, boundingRect.width(),
-                boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
-
-        int deltaX = mTempLocation[0] - boundingRect.left;
-        int deltaY = mTempLocation[1] - boundingRect.top;
-        if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
-            for (View v: dup) {
-                LayoutParams lp = (LayoutParams) v.getLayoutParams();
-                lp.tmpCellX += deltaX;
-                lp.tmpCellY += deltaY;
-            }
-            success = true;
-        }
-        for (View v: dup) {
-            LayoutParams lp = (LayoutParams) v.getLayoutParams();
-            markCellsForView(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan,
-                    lp.cellVSpan, mTmpOccupied, true);
-        }
-        return success;
-    }
-
     private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
-            int[] direction) {
+            int[] direction, boolean push, ItemConfiguration currentState) {
         if (views.size() == 0) return true;
-        boolean success = false;
 
-        // We construct a rect which represents the entire group of views
+        boolean success = false;
         Rect boundingRect = null;
+        // We construct a rect which represents the entire group of views passed in
         for (View v: views) {
-            LayoutParams lp = (LayoutParams) v.getLayoutParams();
-            markCellsForView(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan,
-                    lp.cellVSpan, mTmpOccupied, false);
+            CellAndSpan c = currentState.map.get(v);
             if (boundingRect == null) {
-                boundingRect = new Rect(lp.tmpCellX, lp.tmpCellY, lp.tmpCellX + lp.cellHSpan,
-                        lp.tmpCellY + lp.cellVSpan);
+                boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
             } else {
-                boundingRect.union(lp.tmpCellX, lp.tmpCellY, lp.tmpCellX + lp.cellHSpan,
-                        lp.tmpCellY + lp.cellVSpan);
+                boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
             }
         }
+
+        @SuppressWarnings("unchecked")
+        ArrayList<View> dup = (ArrayList<View>) views.clone();
+        // We try and expand the group of views in the direction vector passed, based on
+        // whether they are physically adjacent, ie. based on "push mechanics".
+        while (push && addViewInDirection(dup, boundingRect, direction, mTmpOccupied,
+                currentState)) {
+        }
+
+        // Mark the occupied state as false for the group of views we want to move.
+        for (View v: dup) {
+            CellAndSpan c = currentState.map.get(v);
+            markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
+        }
+
         boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
         int top = boundingRect.top;
         int left = boundingRect.left;
-        for (View v: views) {
-            LayoutParams lp = (LayoutParams) v.getLayoutParams();
-            markCellsForView(lp.tmpCellX - left, lp.tmpCellY - top, lp.cellHSpan,
-                    lp.cellVSpan, blockOccupied, true); 
+        // We mark more precisely which parts of the bounding rect are truly occupied, allowing
+        // for tetris-style interlocking.
+        for (View v: dup) {
+            CellAndSpan c = currentState.map.get(v);
+            markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
         }
 
         markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
 
-        // TODO: this bounding rect may not be completely filled, lets be more precise about this
-        // check.
-        findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
-                boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
+        if (push) {
+            findNearestAreaInDirection(boundingRect.left, boundingRect.top, boundingRect.width(),
+                    boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
+        } else {
+            findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
+                    boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
+        }
 
-        int deltaX = mTempLocation[0] - boundingRect.left;
-        int deltaY = mTempLocation[1] - boundingRect.top;
+        // If we successfuly found a location by pushing the block of views, we commit it
         if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
-            for (View v: views) {
-                LayoutParams lp = (LayoutParams) v.getLayoutParams();
-                lp.tmpCellX += deltaX;
-                lp.tmpCellY += deltaY;
+            int deltaX = mTempLocation[0] - boundingRect.left;
+            int deltaY = mTempLocation[1] - boundingRect.top;
+            for (View v: dup) {
+                CellAndSpan c = currentState.map.get(v);
+                c.x += deltaX;
+                c.y += deltaY;
             }
             success = true;
         }
-        for (View v: views) {
-            LayoutParams lp = (LayoutParams) v.getLayoutParams();
-            markCellsForView(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan,
-                    lp.cellVSpan, mTmpOccupied, true);
+
+        // In either case, we set the occupied array as marked for the location of the views
+        for (View v: dup) {
+            CellAndSpan c = currentState.map.get(v);
+            markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
         }
         return success;
     }
@@ -1771,25 +1723,26 @@
     }
 
     private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
-            View ignoreView) {
-        mIntersectingViews.clear();
+            View ignoreView, ItemConfiguration solution) {
 
+        mIntersectingViews.clear();
         mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
 
+        // Mark the desired location of the view currently being dragged.
         if (ignoreView != null) {
-            LayoutParams lp = (LayoutParams) ignoreView.getLayoutParams();
-            lp.tmpCellX = cellX;
-            lp.tmpCellY = cellY;
+            CellAndSpan c = solution.map.get(ignoreView);
+            c.x = cellX;
+            c.y = cellY;
         }
 
-        int childCount = mShortcutsAndWidgets.getChildCount();
+        //int childCount = mChildren.getChildCount();
         Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
         Rect r1 = new Rect();
-        for (int i = 0; i < childCount; i++) {
-            View child = mShortcutsAndWidgets.getChildAt(i);
+        for (View child: solution.map.keySet()) {
             if (child == ignoreView) continue;
+            CellAndSpan c = solution.map.get(child);
             LayoutParams lp = (LayoutParams) child.getLayoutParams();
-            r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
+            r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
             if (Rect.intersects(r0, r1)) {
                 if (!lp.canReorder) {
                     return false;
@@ -1798,27 +1751,28 @@
             }
         }
 
-        if (pushViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction)) {
+        // We try to move the intersecting views as a block using the push mechanic
+        if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, true, solution)) {
             return true;
         }
         // Try the opposite direction
         direction[0] *= -1;
         direction[1] *= -1;
-        if (pushViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction)) {
+        if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, true, solution)) {
             return true;
         }
         // Switch the direction back
         direction[0] *= -1;
         direction[1] *= -1;
 
-        // First we try moving the views as a block
-        if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction)) {
+        // Next we try moving the views as a block , but without requiring the push mechanic
+        if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, false, solution)) {
             return true;
         }
 
         // Ok, they couldn't move as a block, let's move them individually
         for (View v : mIntersectingViews) {
-            if (!addViewToTempLocation(v, mOccupiedRect, direction)) {
+            if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
                 return false;
             }
         }
@@ -1842,11 +1796,21 @@
         }
     }
 
+    private void copyOccupiedArray(boolean[][] occupied) {
+        for (int i = 0; i < mCountX; i++) {
+            for (int j = 0; j < mCountY; j++) {
+                occupied[i][j] = mOccupied[i][j];
+            }
+        }
+    }
+
     ItemConfiguration simpleSwap(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
             int spanY, int[] direction, View dragView, boolean decX, ItemConfiguration solution) {
-        // This creates a copy of the current occupied array, omitting the current view being
-        // dragged
-        resetTempLayoutToCurrent(dragView);
+        // Copy the current state into the solution. This solution will be manipulated as necessary.
+        copyCurrentStateToSolution(solution, false);
+        // Copy the current occupied array into the temporary occupied array. This array will be
+        // manipulated as necessary to find a solution.
+        copyOccupiedArray(mTmpOccupied);
 
         // We find the nearest cell into which we would place the dragged item, assuming there's
         // nothing in its way.
@@ -1856,7 +1820,8 @@
         boolean success = false;
         // First we try the exact nearest position of the item being dragged,
         // we will then want to try to move this around to other neighbouring positions
-        success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView);
+        success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
+                solution);
 
         if (!success) {
             // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
@@ -1875,7 +1840,6 @@
             solution.dragViewY = result[1];
             solution.dragViewSpanX = spanX;
             solution.dragViewSpanY = spanY;
-            copyCurrentStateToSolution(solution, true);
         }
         return solution;
     }
@@ -1885,13 +1849,13 @@
         for (int i = 0; i < childCount; i++) {
             View child = mShortcutsAndWidgets.getChildAt(i);
             LayoutParams lp = (LayoutParams) child.getLayoutParams();
-            Point p;
+            CellAndSpan c;
             if (temp) {
-                p = new Point(lp.tmpCellX, lp.tmpCellY);
+                c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
             } else {
-                p = new Point(lp.cellX, lp.cellY);
+                c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
             }
-            solution.map.put(child, p);
+            solution.map.put(child, c);
         }
     }
 
@@ -1907,12 +1871,13 @@
             View child = mShortcutsAndWidgets.getChildAt(i);
             if (child == dragView) continue;
             LayoutParams lp = (LayoutParams) child.getLayoutParams();
-            Point p = solution.map.get(child);
-            if (p != null) {
-                lp.tmpCellX = p.x;
-                lp.tmpCellY = p.y;
-                markCellsForView(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan,
-                        mTmpOccupied, true);
+            CellAndSpan c = solution.map.get(child);
+            if (c != null) {
+                lp.tmpCellX = c.x;
+                lp.tmpCellY = c.y;
+                lp.cellHSpan = c.spanX;
+                lp.cellVSpan = c.spanY;
+                markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
             }
         }
         markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
@@ -1934,12 +1899,12 @@
             View child = mShortcutsAndWidgets.getChildAt(i);
             if (child == dragView) continue;
             LayoutParams lp = (LayoutParams) child.getLayoutParams();
-            Point p = solution.map.get(child);
-            if (p != null) {
-                if (lp.cellX != p.x || lp.cellY != p.y) {
-                    animateChildToPosition(child, p.x, p.y, 150, 0, DESTRUCTIVE_REORDER, false);
+            CellAndSpan c = solution.map.get(child);
+            if (c != null) {
+                if (lp.cellX != c.x || lp.cellY != c.y) {
+                    animateChildToPosition(child, c.x, c.y, 150, 0, DESTRUCTIVE_REORDER, false);
                 }
-                markCellsForView(p.x, p.y, lp.cellHSpan, lp.cellVSpan, occupied, true);
+                markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
             }
         }
         if (commitDragView) {
@@ -1970,22 +1935,6 @@
         }
     }
 
-    private void resetTempLayoutToCurrent(View ignoreView) {
-        for (int i = 0; i < mCountX; i++) {
-            for (int j = 0; j < mCountY; j++) {
-                mTmpOccupied[i][j] = mOccupied[i][j];
-            }
-        }
-        int childCount = mShortcutsAndWidgets.getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            View child = mShortcutsAndWidgets.getChildAt(i);
-            if (child == ignoreView) continue;
-            LayoutParams lp = (LayoutParams) child.getLayoutParams();
-            lp.tmpCellX = lp.cellX;
-            lp.tmpCellY = lp.cellY;
-        }
-    }
-
     ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
             int spanX, int spanY, View dragView, ItemConfiguration solution) {
         int[] result = new int[2];
@@ -2070,7 +2019,6 @@
         if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
             setUseTempCoords(false);
         }
-        boolean[][] occupied = mOccupied;
 
         mShortcutsAndWidgets.requestLayout();
         return result;
@@ -2085,16 +2033,24 @@
     }
 
     private class ItemConfiguration {
-        HashMap<View, Point> map = new HashMap<View, Point>();
+        HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
         boolean isSolution = false;
         int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
 
         int area() {
             return dragViewSpanX * dragViewSpanY;
         }
-        void clear() {
-            map.clear();
-            isSolution = false;
+    }
+
+    private class CellAndSpan {
+        int x, y;
+        int spanX, spanY;
+
+        public CellAndSpan(int x, int y, int spanX, int spanY) {
+            this.x = x;
+            this.y = y;
+            this.spanX = spanX;
+            this.spanY = spanY;
         }
     }
 
diff --git a/src/com/android/launcher2/DeleteDropTarget.java b/src/com/android/launcher2/DeleteDropTarget.java
index 6f45590..7e4225b 100644
--- a/src/com/android/launcher2/DeleteDropTarget.java
+++ b/src/com/android/launcher2/DeleteDropTarget.java
@@ -105,6 +105,15 @@
         return (d.dragSource instanceof Workspace) && (d.dragInfo instanceof FolderInfo);
     }
 
+    private void setHoverColor() {
+        mCurrentDrawable.startTransition(mTransitionDuration);
+        setTextColor(mHoverColor);
+    }
+    private void resetHoverColor() {
+        mCurrentDrawable.resetTransition();
+        setTextColor(mOriginalTextColor);
+    }
+
     @Override
     public boolean acceptDrop(DragObject d) {
         // We can remove everything including App shortcuts, folders, widgets, etc.
@@ -140,8 +149,7 @@
         mCurrentDrawable = (TransitionDrawable) getCompoundDrawables()[0];
 
         mActive = isVisible;
-        mCurrentDrawable.resetTransition();
-        setTextColor(mOriginalTextColor);
+        resetHoverColor();
         ((ViewGroup) getParent()).setVisibility(isVisible ? View.VISIBLE : View.GONE);
         if (getText().length() > 0) {
             setText(isUninstall ? R.string.delete_target_uninstall_label
@@ -158,16 +166,14 @@
     public void onDragEnter(DragObject d) {
         super.onDragEnter(d);
 
-        mCurrentDrawable.startTransition(mTransitionDuration);
-        setTextColor(mHoverColor);
+        setHoverColor();
     }
 
     public void onDragExit(DragObject d) {
         super.onDragExit(d);
 
         if (!d.dragComplete) {
-            mCurrentDrawable.resetTransition();
-            setTextColor(mOriginalTextColor);
+            resetHoverColor();
         } else {
             // Restore the hover color if we are deleting
             d.dragView.setColor(mHoverColor);
@@ -349,9 +355,15 @@
     }
 
     public void onFlingToDelete(final DragObject d, int x, int y, PointF vel) {
+        final boolean isAllApps = d.dragSource instanceof AppsCustomizePagedView;
+
         // Don't highlight the icon as it's animating
         d.dragView.setColor(0);
         d.dragView.updateInitialScaleToCurrentScale();
+        // Don't highlight the target if we are flinging from AllApps
+        if (isAllApps) {
+            resetHoverColor();
+        }
 
         if (mFlingDeleteMode == MODE_FLING_DELETE_TO_TRASH) {
             // Defer animating out the drop target if we are animating to it
@@ -396,8 +408,14 @@
             @Override
             public void run() {
                 mSearchDropTargetBar.onDragEnd();
-                mLauncher.exitSpringLoadedDragMode();
-                completeDrop(d);
+
+                // If we are dragging from AllApps, then we allow AppsCustomizePagedView to clean up
+                // itself, otherwise, complete the drop to initiate the deletion process
+                if (!isAllApps) {
+                    mLauncher.exitSpringLoadedDragMode();
+                    completeDrop(d);
+                }
+                mLauncher.getDragController().onDeferredEndFling(d);
             }
         };
         dragLayer.animateView(d.dragView, updateCb, duration, tInterpolator, onAnimationEndRunnable,
diff --git a/src/com/android/launcher2/DragController.java b/src/com/android/launcher2/DragController.java
index 2a88925..5a8617c 100644
--- a/src/com/android/launcher2/DragController.java
+++ b/src/com/android/launcher2/DragController.java
@@ -366,7 +366,7 @@
             mDragObject.deferDragViewCleanupPostAnimation = false;
             mDragObject.cancelled = true;
             mDragObject.dragComplete = true;
-            mDragObject.dragSource.onDropCompleted(null, mDragObject, false);
+            mDragObject.dragSource.onDropCompleted(null, mDragObject, false, false);
         }
         endDrag();
     }
@@ -422,6 +422,10 @@
         }
     }
 
+    void onDeferredEndFling(DropTarget.DragObject d) {
+        d.dragSource.onFlingToDeleteCompleted();
+    }
+
     /**
      * Clamps the position to the drag layer bounds.
      */
@@ -665,7 +669,7 @@
                     vel);
             accepted = true;
         }
-        mDragObject.dragSource.onDropCompleted((View) mFlingToDeleteDropTarget, mDragObject,
+        mDragObject.dragSource.onDropCompleted((View) mFlingToDeleteDropTarget, mDragObject, true,
                 accepted);
     }
 
@@ -684,7 +688,7 @@
                 accepted = true;
             }
         }
-        mDragObject.dragSource.onDropCompleted((View) dropTarget, mDragObject, accepted);
+        mDragObject.dragSource.onDropCompleted((View) dropTarget, mDragObject, false, accepted);
     }
 
     private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) {
diff --git a/src/com/android/launcher2/DragSource.java b/src/com/android/launcher2/DragSource.java
index a654b93..5440477 100644
--- a/src/com/android/launcher2/DragSource.java
+++ b/src/com/android/launcher2/DragSource.java
@@ -25,6 +25,21 @@
  *
  */
 public interface DragSource {
+    /**
+     * @return whether items dragged from this source supports
+     */
     boolean supportsFlingToDelete();
-    void onDropCompleted(View target, DragObject d, boolean success);
+
+    /**
+     * A callback specifically made back to the source after an item from this source has been flung
+     * to be deleted on a DropTarget.  In such a situation, this method will be called after
+     * onDropCompleted, and more importantly, after the fling animation has completed.
+     */
+    void onFlingToDeleteCompleted();
+
+    /**
+     * A callback made back to the source after an item from this source has been dropped on a
+     * DropTarget.
+     */
+    void onDropCompleted(View target, DragObject d, boolean isFlingToDelete, boolean success);
 }
diff --git a/src/com/android/launcher2/Folder.java b/src/com/android/launcher2/Folder.java
index 6856a09..c502fb7 100644
--- a/src/com/android/launcher2/Folder.java
+++ b/src/com/android/launcher2/Folder.java
@@ -44,6 +44,7 @@
 import android.widget.TextView;
 
 import com.android.launcher.R;
+import com.android.launcher2.DropTarget.DragObject;
 import com.android.launcher2.FolderInfo.FolderListener;
 
 import java.util.ArrayList;
@@ -619,7 +620,8 @@
         mReorderAlarm.cancelAlarm();
     }
 
-    public void onDropCompleted(View target, DragObject d, boolean success) {
+    public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete,
+            boolean success) {
         if (success) {
             if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon) {
                 replaceFolderWithFinalItem();
@@ -653,10 +655,20 @@
         updateItemLocationsInDatabase();
     }
 
+    @Override
     public boolean supportsFlingToDelete() {
         return true;
     }
 
+    public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
+        // Do nothing
+    }
+
+    @Override
+    public void onFlingToDeleteCompleted() {
+        // Do nothing
+    }
+
     private void updateItemLocationsInDatabase() {
         ArrayList<View> list = getItemsInReadingOrder();
         for (int i = 0; i < list.size(); i++) {
@@ -928,10 +940,6 @@
         mInfo.add(item);
     }
 
-    public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
-        // Do nothing
-    }
-
     public void onAdd(ShortcutInfo item) {
         mItemsInvalidated = true;
         // If the item was dropped onto this open folder, we have done the work associated
diff --git a/src/com/android/launcher2/InstallShortcutReceiver.java b/src/com/android/launcher2/InstallShortcutReceiver.java
index e04ce64..4c0974f 100644
--- a/src/com/android/launcher2/InstallShortcutReceiver.java
+++ b/src/com/android/launcher2/InstallShortcutReceiver.java
@@ -17,9 +17,9 @@
 package com.android.launcher2;
 
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.widget.Toast;
@@ -27,10 +27,21 @@
 import com.android.launcher.R;
 
 import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
 
 public class InstallShortcutReceiver extends BroadcastReceiver {
     public static final String ACTION_INSTALL_SHORTCUT =
             "com.android.launcher.action.INSTALL_SHORTCUT";
+    public static final String NEW_APPS_PAGE_KEY = "apps.new.page";
+    public static final String NEW_APPS_LIST_KEY = "apps.new.list";
+
+    public static final int NEW_SHORTCUT_BOUNCE_DURATION = 450;
+    public static final int NEW_SHORTCUT_STAGGER_DELAY = 75;
+
+    private static final int INSTALL_SHORTCUT_SUCCESSFUL = 0;
+    private static final int INSTALL_SHORTCUT_IS_DUPLICATE = -1;
+    private static final int INSTALL_SHORTCUT_NO_SPACE = -2;
 
     // A mime-type representing shortcut data
     public static final String SHORTCUT_MIMETYPE =
@@ -42,6 +53,8 @@
         if (!ACTION_INSTALL_SHORTCUT.equals(data.getAction())) {
             return;
         }
+        String spKey = LauncherApplication.getSharedPreferencesKey();
+        SharedPreferences sp = context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
 
         final int screen = Launcher.getScreen();
         final Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
@@ -62,26 +75,35 @@
         }
 
         final ArrayList<ItemInfo> items = LauncherModel.getItemsInLocalCoordinates(context);
-        final boolean shortcutExists = LauncherModel.shortcutExists(context, name, intent);
-        final String[] errorMsgs = {""};
+        final boolean exists = LauncherModel.shortcutExists(context, name, intent);
+        final int[] result = {INSTALL_SHORTCUT_SUCCESSFUL};
 
-        if (!installShortcut(context, data, items, name, intent, screen, shortcutExists,
-                errorMsgs)) {
-            // The target screen is full, let's try the other screens
-            for (int i = 0; i < Launcher.SCREEN_COUNT; i++) {
-                if (i != screen && installShortcut(context, data, items, name, intent, i,
-                        shortcutExists, errorMsgs)) break;
+        // Try adding the target to the workspace screens incrementally, starting at the current
+        // screen and alternating between +1, -1, +2, -2, etc. (using ~ ceil(i/2f)*(-1)^(i-1))
+        boolean found = false;
+        for (int i = 0; i < (2 * Launcher.SCREEN_COUNT) + 1 && !found; ++i) {
+            int si = screen + (int) ((i / 2f) + 0.5f) * ((i % 2 == 1) ? 1 : -1);
+            if (0 <= si && si < Launcher.SCREEN_COUNT) {
+                found = installShortcut(context, data, items, name, intent, si, exists, sp, result);
             }
         }
 
-        if (!errorMsgs[0].isEmpty()) {
-            Toast.makeText(context, errorMsgs[0],
-                    Toast.LENGTH_SHORT).show();
+        // We only report error messages (duplicate shortcut or out of space) as the add-animation
+        // will provide feedback otherwise
+        if (!found) {
+            if (result[0] == INSTALL_SHORTCUT_NO_SPACE) {
+                Toast.makeText(context, context.getString(R.string.out_of_space),
+                        Toast.LENGTH_SHORT).show();
+            } else if (result[0] == INSTALL_SHORTCUT_IS_DUPLICATE) {
+                Toast.makeText(context, context.getString(R.string.shortcut_duplicate, name),
+                        Toast.LENGTH_SHORT).show();
+            }
         }
     }
 
     private boolean installShortcut(Context context, Intent data, ArrayList<ItemInfo> items,
-            String name, Intent intent, int screen, boolean shortcutExists, String[] errorMsgs) {
+            String name, Intent intent, int screen, boolean shortcutExists,
+            SharedPreferences sharedPrefs, int[] result) {
         if (findEmptyCell(context, items, mCoordinates, screen)) {
             if (intent != null) {
                 if (intent.getAction() == null) {
@@ -92,23 +114,35 @@
                 // different places)
                 boolean duplicate = data.getBooleanExtra(Launcher.EXTRA_SHORTCUT_DUPLICATE, true);
                 if (duplicate || !shortcutExists) {
+                    // If the new app is going to fall into the same page as before, then just
+                    // continue adding to the current page
+                    int newAppsScreen = sharedPrefs.getInt(NEW_APPS_PAGE_KEY, screen);
+                    Set<String> newApps = new HashSet<String>();
+                    if (newAppsScreen == screen) {
+                        newApps = sharedPrefs.getStringSet(NEW_APPS_LIST_KEY, newApps);
+                    }
+                    newApps.add(intent.toUri(0).toString());
+                    sharedPrefs.edit()
+                               .putInt(NEW_APPS_PAGE_KEY, screen)
+                               .putStringSet(NEW_APPS_LIST_KEY, newApps)
+                               .commit();
+
+                    // Update the Launcher db
                     LauncherApplication app = (LauncherApplication) context.getApplicationContext();
                     ShortcutInfo info = app.getModel().addShortcut(context, data,
-                            LauncherSettings.Favorites.CONTAINER_DESKTOP, screen, mCoordinates[0],
-                            mCoordinates[1], true);
-                    if (info != null) {
-                        errorMsgs[0] = context.getString(R.string.shortcut_installed, name);
-                    } else {
+                            LauncherSettings.Favorites.CONTAINER_DESKTOP, screen,
+                            mCoordinates[0], mCoordinates[1], true);
+                    if (info == null) {
                         return false;
                     }
                 } else {
-                    errorMsgs[0] = context.getString(R.string.shortcut_duplicate, name);
+                    result[0] = INSTALL_SHORTCUT_IS_DUPLICATE;
                 }
 
                 return true;
             }
         } else {
-            errorMsgs[0] = context.getString(R.string.out_of_space);
+            result[0] = INSTALL_SHORTCUT_NO_SPACE;
         }
 
         return false;
diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java
index 0c1b76f..9494d27 100644
--- a/src/com/android/launcher2/Launcher.java
+++ b/src/com/android/launcher2/Launcher.java
@@ -55,6 +55,7 @@
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
+import android.os.Debug;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.Message;
@@ -84,6 +85,7 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.view.animation.AccelerateInterpolator;
+import android.view.animation.BounceInterpolator;
 import android.view.animation.DecelerateInterpolator;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Advanceable;
@@ -103,7 +105,12 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
  * Default launcher application.
@@ -253,6 +260,10 @@
     // it from the context.
     private SharedPreferences mSharedPrefs;
 
+    // Holds the page that we need to animate to, and the icon views that we need to animate up
+    // when we scroll to that page on resume.
+    private int mNewShortcutAnimatePage = -1;
+    private ArrayList<View> mNewShortcutAnimateViews = new ArrayList<View>();
 
     private BubbleTextView mWaitingForResume;
 
@@ -280,7 +291,8 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         LauncherApplication app = ((LauncherApplication)getApplication());
-        mSharedPrefs = getSharedPreferences(PREFS_KEY, Context.MODE_PRIVATE);
+        mSharedPrefs = getSharedPreferences(LauncherApplication.getSharedPreferencesKey(),
+                Context.MODE_PRIVATE);
         mModel = app.setLauncher(this);
         mIconCache = app.getIconCache();
         mDragController = new DragController(this);
@@ -317,7 +329,7 @@
         }
 
         if (!mRestoring) {
-            mModel.startLoader(this, true);
+            mModel.startLoader(true);
         }
 
         if (!mModel.isAllAppsLoaded()) {
@@ -593,10 +605,11 @@
     @Override
     protected void onResume() {
         super.onResume();
+
         mPaused = false;
         if (mRestoring || mOnResumeNeedsLoad) {
             mWorkspaceLoading = true;
-            mModel.startLoader(this, true);
+            mModel.startLoader(true);
             mRestoring = false;
             mOnResumeNeedsLoad = false;
         }
@@ -725,7 +738,7 @@
             showAllApps(false);
         }
 
-        final int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN, -1);
+        int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN, -1);
         if (currentScreen > -1) {
             mWorkspace.setCurrentPage(currentScreen);
         }
@@ -2184,7 +2197,7 @@
 
                 if (mWorkspaceLoading) {
                     lockAllApps();
-                    mModel.startLoader(Launcher.this, false);
+                    mModel.startLoader(false);
                 } else {
                     final FolderIcon folderIcon = (FolderIcon)
                             mWorkspace.getViewForTag(mFolderInfo);
@@ -2196,7 +2209,7 @@
                     } else {
                         lockAllApps();
                         mWorkspaceLoading = true;
-                        mModel.startLoader(Launcher.this, false);
+                        mModel.startLoader(false);
                     }
                 }
             }
@@ -3076,7 +3089,6 @@
         }
     }
 
-
     /**
      * Refreshes the shortcuts shown on the workspace.
      *
@@ -3085,6 +3097,8 @@
     public void startBinding() {
         final Workspace workspace = mWorkspace;
 
+        mNewShortcutAnimatePage = -1;
+        mNewShortcutAnimateViews.clear();
         mWorkspace.clearDropTargets();
         int count = workspace.getChildCount();
         for (int i = 0; i < count; i++) {
@@ -3106,8 +3120,12 @@
     public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end) {
         setLoadOnResume();
 
-        final Workspace workspace = mWorkspace;
-        for (int i=start; i<end; i++) {
+        // Get the list of added shortcuts and intersect them with the set of shortcuts here
+        Set<String> newApps = new HashSet<String>();
+        newApps = mSharedPrefs.getStringSet(InstallShortcutReceiver.NEW_APPS_LIST_KEY, newApps);
+
+        Workspace workspace = mWorkspace;
+        for (int i = start; i < end; i++) {
             final ItemInfo item = shortcuts.get(i);
 
             // Short circuit if we are loading dock items for a configuration which has no dock
@@ -3119,9 +3137,23 @@
             switch (item.itemType) {
                 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
-                    View shortcut = createShortcut((ShortcutInfo)item);
+                    ShortcutInfo info = (ShortcutInfo) item;
+                    String uri = info.intent.toUri(0).toString();
+                    View shortcut = createShortcut(info);
                     workspace.addInScreen(shortcut, item.container, item.screen, item.cellX,
                             item.cellY, 1, 1, false);
+                    if (newApps.contains(uri)) {
+                        newApps.remove(uri);
+
+                        // Prepare the view to be animated up
+                        shortcut.setAlpha(0f);
+                        shortcut.setScaleX(0f);
+                        shortcut.setScaleY(0f);
+                        mNewShortcutAnimatePage = item.screen;
+                        if (!mNewShortcutAnimateViews.contains(shortcut)) {
+                            mNewShortcutAnimateViews.add(shortcut);
+                        }
+                    }
                     break;
                 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                     FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
@@ -3132,6 +3164,7 @@
                     break;
             }
         }
+
         workspace.requestLayout();
     }
 
@@ -3169,11 +3202,6 @@
         item.hostView.setAppWidget(appWidgetId, appWidgetInfo);
         item.hostView.setTag(item);
 
-        // We need to load the minimum span and embed it into the item info
-        int[] minSpan = getMinSpanForWidget(appWidgetInfo, null);
-        item.minSpanX = minSpan[0];
-        item.minSpanY = minSpan[1];
-
         workspace.addInScreen(item.hostView, item.container, item.screen, item.cellX,
                 item.cellY, item.spanX, item.spanY, false);
 
@@ -3207,8 +3235,6 @@
             mSavedInstanceState = null;
         }
 
-        mWorkspaceLoading = false;
-
         // 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.
         for (int i = 0; i < sPendingAddList.size(); i++) {
@@ -3220,7 +3246,72 @@
         // package changes in bindSearchablesChanged()
         updateAppMarketIcon();
 
-        mWorkspace.postDelayed(mBuildLayersRunnable, 500);
+        // Animate up any icons as necessary
+        if (mVisible || mWorkspaceLoading) {
+            Runnable newAppsRunnable = new Runnable() {
+                @Override
+                public void run() {
+                    runNewAppsAnimation();
+                }
+            };
+            if (mNewShortcutAnimatePage > -1 &&
+                    mNewShortcutAnimatePage != mWorkspace.getCurrentPage()) {
+                mWorkspace.snapToPage(mNewShortcutAnimatePage, newAppsRunnable);
+            } else {
+                newAppsRunnable.run();
+            }
+        }
+
+        mWorkspaceLoading = false;
+    }
+
+    /**
+     * Runs a new animation that scales up icons that were added while Launcher was in the
+     * background.
+     */
+    private void runNewAppsAnimation() {
+        AnimatorSet anim = new AnimatorSet();
+        Collection<Animator> bounceAnims = new ArrayList<Animator>();
+        Collections.sort(mNewShortcutAnimateViews, new Comparator<View>() {
+            @Override
+            public int compare(View a, View b) {
+                CellLayout.LayoutParams alp = (CellLayout.LayoutParams) a.getLayoutParams();
+                CellLayout.LayoutParams blp = (CellLayout.LayoutParams) b.getLayoutParams();
+                int cellCountX = LauncherModel.getCellCountX();
+                return (alp.cellY * cellCountX + alp.cellX) - (blp.cellY * cellCountX + blp.cellX);
+            }
+        });
+        for (int i = 0; i < mNewShortcutAnimateViews.size(); ++i) {
+            View v = mNewShortcutAnimateViews.get(i);
+            ValueAnimator bounceAnim = ObjectAnimator.ofPropertyValuesHolder(v,
+                    PropertyValuesHolder.ofFloat("alpha", 1f),
+                    PropertyValuesHolder.ofFloat("scaleX", 1f),
+                    PropertyValuesHolder.ofFloat("scaleY", 1f));
+            bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
+            bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY);
+            bounceAnim.setInterpolator(new SmoothPagedView.OvershootInterpolator());
+            bounceAnims.add(bounceAnim);
+        }
+        anim.playTogether(bounceAnims);
+        anim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mWorkspace.postDelayed(mBuildLayersRunnable, 500);
+            }
+        });
+        anim.start();
+
+        // Clean up
+        mNewShortcutAnimatePage = -1;
+        mNewShortcutAnimateViews.clear();
+        new Thread("clearNewAppsThread") {
+            public void run() {
+                mSharedPrefs.edit()
+                            .putInt(InstallShortcutReceiver.NEW_APPS_PAGE_KEY, -1)
+                            .putStringSet(InstallShortcutReceiver.NEW_APPS_LIST_KEY, null)
+                            .commit();
+            }
+        }.start();
     }
 
     @Override
@@ -3363,7 +3454,6 @@
     }
 
     /* Cling related */
-    private static final String PREFS_KEY = "com.android.launcher2.prefs";
     private boolean isClingsEnabled() {
         // disable clings when running in a test harness
         if(ActivityManager.isRunningInTestHarness()) return false;
diff --git a/src/com/android/launcher2/LauncherApplication.java b/src/com/android/launcher2/LauncherApplication.java
index 47ce0b7..ef1eb5f 100644
--- a/src/com/android/launcher2/LauncherApplication.java
+++ b/src/com/android/launcher2/LauncherApplication.java
@@ -37,6 +37,7 @@
     private static boolean sIsScreenLarge;
     private static float sScreenDensity;
     private static int sLongPressTimeout = 300;
+    private static final String sSharedPreferencesKey = "com.android.launcher2.prefs";
     WeakReference<LauncherProvider> mLauncherProvider;
 
     @Override
@@ -94,7 +95,10 @@
     private final ContentObserver mFavoritesObserver = new ContentObserver(new Handler()) {
         @Override
         public void onChange(boolean selfChange) {
-            mModel.startLoader(LauncherApplication.this, false);
+            // If the database has ever changed, then we really need to force a reload of the
+            // workspace on the next load
+            mModel.resetLoadedState(false, true);
+            mModel.startLoaderFromBackground();
         }
     };
 
@@ -119,6 +123,10 @@
         return mLauncherProvider.get();
     }
 
+    public static String getSharedPreferencesKey() {
+        return sSharedPreferencesKey;
+    }
+
     public static boolean isScreenLarge() {
         return sIsScreenLarge;
     }
diff --git a/src/com/android/launcher2/LauncherModel.java b/src/com/android/launcher2/LauncherModel.java
index 159ddb0..30eb86c 100644
--- a/src/com/android/launcher2/LauncherModel.java
+++ b/src/com/android/launcher2/LauncherModel.java
@@ -649,19 +649,24 @@
     }
 
     private void forceReload() {
-        synchronized (mLock) {
-            // Stop any existing loaders first, so they don't set mAllAppsLoaded or
-            // mWorkspaceLoaded to true later
-            stopLoaderLocked();
-            mAllAppsLoaded = false;
-            mWorkspaceLoaded = false;
-        }
+        resetLoadedState(true, true);
+
         // Do this here because if the launcher activity is running it will be restarted.
         // If it's not running startLoaderFromBackground will merely tell it that it needs
         // to reload.
         startLoaderFromBackground();
     }
 
+    public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) {
+        synchronized (mLock) {
+            // Stop any existing loaders first, so they don't set mAllAppsLoaded or
+            // mWorkspaceLoaded to true later
+            stopLoaderLocked();
+            if (resetAllAppsLoaded) mAllAppsLoaded = false;
+            if (resetWorkspaceLoaded) mWorkspaceLoaded = false;
+        }
+    }
+
     /**
      * When the launcher is in the background, it's possible for it to miss paired
      * configuration changes.  So whenever we trigger the loader from the background
@@ -680,7 +685,7 @@
             }
         }
         if (runLoader) {
-            startLoader(mApp, false);
+            startLoader(false);
         }
     }
 
@@ -698,7 +703,7 @@
         return isLaunching;
     }
 
-    public void startLoader(Context context, boolean isLaunching) {
+    public void startLoader(boolean isLaunching) {
         synchronized (mLock) {
             if (DEBUG_LOADERS) {
                 Log.d(TAG, "startLoader isLaunching=" + isLaunching);
@@ -709,7 +714,7 @@
                 // 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(context, isLaunching);
+                mLoaderTask = new LoaderTask(mApp, isLaunching);
                 sWorkerThread.setPriority(Thread.NORM_PRIORITY);
                 sWorker.post(mLoaderTask);
             }
diff --git a/src/com/android/launcher2/PagedView.java b/src/com/android/launcher2/PagedView.java
index 0854508..5434704 100644
--- a/src/com/android/launcher2/PagedView.java
+++ b/src/com/android/launcher2/PagedView.java
@@ -62,7 +62,8 @@
     // the min drag distance for a fling to register, to prevent random page shifts
     private static final int MIN_LENGTH_FOR_FLING = 25;
 
-    private static final int PAGE_SNAP_ANIMATION_DURATION = 550;
+    protected static final int PAGE_SNAP_ANIMATION_DURATION = 550;
+    protected static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950;
     protected static final float NANOTIME_DIV = 1000000000.0f;
 
     private static final float OVERSCROLL_ACCELERATE_FACTOR = 2;
diff --git a/src/com/android/launcher2/SmoothPagedView.java b/src/com/android/launcher2/SmoothPagedView.java
index fe763f5..e6414d9 100644
--- a/src/com/android/launcher2/SmoothPagedView.java
+++ b/src/com/android/launcher2/SmoothPagedView.java
@@ -35,11 +35,11 @@
 
     private Interpolator mScrollInterpolator;
 
-    private static class WorkspaceOvershootInterpolator implements Interpolator {
+    public static class OvershootInterpolator implements Interpolator {
         private static final float DEFAULT_TENSION = 1.3f;
         private float mTension;
 
-        public WorkspaceOvershootInterpolator() {
+        public OvershootInterpolator() {
             mTension = DEFAULT_TENSION;
         }
 
@@ -101,7 +101,7 @@
         if (mScrollMode == DEFAULT_MODE) {
             mBaseLineFlingVelocity = 2500.0f;
             mFlingVelocityInfluence = 0.4f;
-            mScrollInterpolator = new WorkspaceOvershootInterpolator();
+            mScrollInterpolator = new OvershootInterpolator();
             mScroller = new Scroller(getContext(), mScrollInterpolator);
         }
     }
@@ -139,9 +139,9 @@
         }
 
         if (settle) {
-            ((WorkspaceOvershootInterpolator) mScrollInterpolator).setDistance(screenDelta);
+            ((OvershootInterpolator) mScrollInterpolator).setDistance(screenDelta);
         } else {
-            ((WorkspaceOvershootInterpolator) mScrollInterpolator).disableSettle();
+            ((OvershootInterpolator) mScrollInterpolator).disableSettle();
         }
 
         velocity = Math.abs(velocity);
diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java
index 8f11612..f1941b3 100644
--- a/src/com/android/launcher2/Workspace.java
+++ b/src/com/android/launcher2/Workspace.java
@@ -59,6 +59,7 @@
 import android.widget.TextView;
 
 import com.android.launcher.R;
+import com.android.launcher2.DropTarget.DragObject;
 import com.android.launcher2.FolderIcon.FolderRingAnimator;
 import com.android.launcher2.InstallWidgetReceiver.WidgetMimeTypeHandlerData;
 import com.android.launcher2.LauncherSettings.Favorites;
@@ -183,6 +184,7 @@
     WallpaperOffsetInterpolator mWallpaperOffset;
     boolean mUpdateWallpaperOffsetImmediately = false;
     private Runnable mDelayedResizeRunnable;
+    private Runnable mDelayedSnapToPageRunnable;
     private int mDisplayWidth;
     private int mDisplayHeight;
     private boolean mIsStaticWallpaper;
@@ -765,6 +767,11 @@
             mDelayedResizeRunnable.run();
             mDelayedResizeRunnable = null;
         }
+
+        if (mDelayedSnapToPageRunnable != null) {
+            mDelayedSnapToPageRunnable.run();
+            mDelayedSnapToPageRunnable = null;
+        }
     }
 
     @Override
@@ -906,6 +913,20 @@
         computeWallpaperScrollRatio(whichPage);
     }
 
+    @Override
+    protected void snapToPage(int whichPage, int duration) {
+        super.snapToPage(whichPage, duration);
+        computeWallpaperScrollRatio(whichPage);
+    }
+
+    protected void snapToPage(int whichPage, Runnable r) {
+        if (mDelayedSnapToPageRunnable != null) {
+            mDelayedSnapToPageRunnable.run();
+        }
+        mDelayedSnapToPageRunnable = r;
+        snapToPage(whichPage, SLOW_PAGE_SNAP_ANIMATION_DURATION);
+    }
+
     private void computeWallpaperScrollRatio(int page) {
         // Here, we determine what the desired scroll would be with and without a layout scale,
         // and compute a ratio between the two. This allows us to adjust the wallpaper offset
@@ -1686,9 +1707,9 @@
                         .setInterpolator(mZoomInInterpolator);
                     anim.play(a);
 
-                    LauncherViewPropertyAnimator alphaAnim =
-                        new LauncherViewPropertyAnimator(cl.getShortcutsAndWidgets());
                     if (mOldAlphas[i] != mNewAlphas[i]) {
+                        LauncherViewPropertyAnimator alphaAnim =
+                            new LauncherViewPropertyAnimator(cl.getShortcutsAndWidgets());
                         alphaAnim.alpha(mNewAlphas[i])
                             .setDuration(duration)
                             .setInterpolator(mZoomInInterpolator);
@@ -2340,10 +2361,6 @@
         }
     }
 
-    public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
-        // Do nothing
-    }
-
     public void setFinalScrollForPageChange(int screen) {
         if (screen >= 0) {
             mSavedScrollX = getScrollX();
@@ -3279,7 +3296,8 @@
     /**
      * Called at the end of a drag which originated on the workspace.
      */
-    public void onDropCompleted(View target, DragObject d, boolean success) {
+    public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete,
+            boolean success) {
         if (success) {
             if (target != this) {
                 if (mDragInfo != null) {
@@ -3336,10 +3354,21 @@
         }
     }
 
+    @Override
     public boolean supportsFlingToDelete() {
         return true;
     }
 
+    @Override
+    public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
+        // Do nothing
+    }
+
+    @Override
+    public void onFlingToDeleteCompleted() {
+        // Do nothing
+    }
+
     public boolean isDropEnabled() {
         return true;
     }