Merge "Removing unused cling, tweaking translation character limits (Bug 11151675)" into jb-ub-now-jetsonic
diff --git a/res/drawable-sw600dp-hdpi/homescreen_blue_normal_holo.9.png b/res/drawable-sw600dp-hdpi/homescreen_blue_normal_holo.9.png
deleted file mode 100644
index 98f7ac8..0000000
--- a/res/drawable-sw600dp-hdpi/homescreen_blue_normal_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/homescreen_blue_strong_holo.9.png b/res/drawable-sw600dp-hdpi/homescreen_blue_strong_holo.9.png
deleted file mode 100644
index 0a511f9..0000000
--- a/res/drawable-sw600dp-hdpi/homescreen_blue_strong_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/ic_allapps.png b/res/drawable-sw600dp-hdpi/ic_allapps.png
deleted file mode 100644
index 8bda435..0000000
--- a/res/drawable-sw600dp-hdpi/ic_allapps.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/ic_allapps_pressed.png b/res/drawable-sw600dp-hdpi/ic_allapps_pressed.png
deleted file mode 100644
index 07ff331..0000000
--- a/res/drawable-sw600dp-hdpi/ic_allapps_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/overscroll_glow_left.9.png b/res/drawable-sw600dp-hdpi/overscroll_glow_left.9.png
deleted file mode 100644
index 3928e2c..0000000
--- a/res/drawable-sw600dp-hdpi/overscroll_glow_left.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/overscroll_glow_right.9.png b/res/drawable-sw600dp-hdpi/overscroll_glow_right.9.png
deleted file mode 100644
index e34de34..0000000
--- a/res/drawable-sw600dp-hdpi/overscroll_glow_right.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/portal_ring_inner_holo.png b/res/drawable-sw600dp-hdpi/portal_ring_inner_holo.png
deleted file mode 100644
index b3be472..0000000
--- a/res/drawable-sw600dp-hdpi/portal_ring_inner_holo.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/portal_ring_inner_nolip_holo.png b/res/drawable-sw600dp-hdpi/portal_ring_inner_nolip_holo.png
deleted file mode 100644
index 96b981c..0000000
--- a/res/drawable-sw600dp-hdpi/portal_ring_inner_nolip_holo.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/portal_ring_outer_holo.png b/res/drawable-sw600dp-hdpi/portal_ring_outer_holo.png
deleted file mode 100644
index bc13a26..0000000
--- a/res/drawable-sw600dp-hdpi/portal_ring_outer_holo.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/homescreen_blue_normal_holo.9.png b/res/drawable-sw600dp-mdpi/homescreen_blue_normal_holo.9.png
deleted file mode 100644
index 3ae4aff..0000000
--- a/res/drawable-sw600dp-mdpi/homescreen_blue_normal_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/homescreen_blue_strong_holo.9.png b/res/drawable-sw600dp-mdpi/homescreen_blue_strong_holo.9.png
deleted file mode 100644
index 31148dd..0000000
--- a/res/drawable-sw600dp-mdpi/homescreen_blue_strong_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/ic_allapps.png b/res/drawable-sw600dp-mdpi/ic_allapps.png
deleted file mode 100644
index e2afea5..0000000
--- a/res/drawable-sw600dp-mdpi/ic_allapps.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/ic_allapps_pressed.png b/res/drawable-sw600dp-mdpi/ic_allapps_pressed.png
deleted file mode 100644
index d409c7e..0000000
--- a/res/drawable-sw600dp-mdpi/ic_allapps_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/overscroll_glow_left.9.png b/res/drawable-sw600dp-mdpi/overscroll_glow_left.9.png
deleted file mode 100644
index 58ec10c..0000000
--- a/res/drawable-sw600dp-mdpi/overscroll_glow_left.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/overscroll_glow_right.9.png b/res/drawable-sw600dp-mdpi/overscroll_glow_right.9.png
deleted file mode 100644
index a986fd3..0000000
--- a/res/drawable-sw600dp-mdpi/overscroll_glow_right.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/portal_ring_inner_holo.png b/res/drawable-sw600dp-mdpi/portal_ring_inner_holo.png
deleted file mode 100644
index 319c074..0000000
--- a/res/drawable-sw600dp-mdpi/portal_ring_inner_holo.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/portal_ring_inner_nolip_holo.png b/res/drawable-sw600dp-mdpi/portal_ring_inner_nolip_holo.png
deleted file mode 100644
index 8537714..0000000
--- a/res/drawable-sw600dp-mdpi/portal_ring_inner_nolip_holo.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/portal_ring_outer_holo.png b/res/drawable-sw600dp-mdpi/portal_ring_outer_holo.png
deleted file mode 100644
index 365dcfc..0000000
--- a/res/drawable-sw600dp-mdpi/portal_ring_outer_holo.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/homescreen_blue_normal_holo.9.png b/res/drawable-sw600dp-xhdpi/homescreen_blue_normal_holo.9.png
deleted file mode 100644
index c25b590..0000000
--- a/res/drawable-sw600dp-xhdpi/homescreen_blue_normal_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/homescreen_blue_strong_holo.9.png b/res/drawable-sw600dp-xhdpi/homescreen_blue_strong_holo.9.png
deleted file mode 100644
index 24ac880..0000000
--- a/res/drawable-sw600dp-xhdpi/homescreen_blue_strong_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/ic_allapps.png b/res/drawable-sw600dp-xhdpi/ic_allapps.png
deleted file mode 100644
index 8fed290..0000000
--- a/res/drawable-sw600dp-xhdpi/ic_allapps.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/ic_allapps_pressed.png b/res/drawable-sw600dp-xhdpi/ic_allapps_pressed.png
deleted file mode 100644
index 786b676..0000000
--- a/res/drawable-sw600dp-xhdpi/ic_allapps_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/overscroll_glow_left.9.png b/res/drawable-sw600dp-xhdpi/overscroll_glow_left.9.png
deleted file mode 100644
index b66dd2f..0000000
--- a/res/drawable-sw600dp-xhdpi/overscroll_glow_left.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/overscroll_glow_right.9.png b/res/drawable-sw600dp-xhdpi/overscroll_glow_right.9.png
deleted file mode 100644
index 3ccce33..0000000
--- a/res/drawable-sw600dp-xhdpi/overscroll_glow_right.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/portal_ring_inner_holo.png b/res/drawable-sw600dp-xhdpi/portal_ring_inner_holo.png
deleted file mode 100644
index d4ce45f..0000000
--- a/res/drawable-sw600dp-xhdpi/portal_ring_inner_holo.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/portal_ring_inner_nolip_holo.png b/res/drawable-sw600dp-xhdpi/portal_ring_inner_nolip_holo.png
deleted file mode 100644
index 9aa13c9..0000000
--- a/res/drawable-sw600dp-xhdpi/portal_ring_inner_nolip_holo.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/portal_ring_outer_holo.png b/res/drawable-sw600dp-xhdpi/portal_ring_outer_holo.png
deleted file mode 100644
index 0106cd6..0000000
--- a/res/drawable-sw600dp-xhdpi/portal_ring_outer_holo.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-xxhdpi/homescreen_blue_normal_holo.9.png b/res/drawable-sw600dp-xxhdpi/homescreen_blue_normal_holo.9.png
deleted file mode 100644
index c661f68..0000000
--- a/res/drawable-sw600dp-xxhdpi/homescreen_blue_normal_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-xxhdpi/homescreen_blue_strong_holo.9.png b/res/drawable-sw600dp-xxhdpi/homescreen_blue_strong_holo.9.png
deleted file mode 100644
index bf6ab97..0000000
--- a/res/drawable-sw600dp-xxhdpi/homescreen_blue_strong_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-xxhdpi/ic_allapps.png b/res/drawable-sw600dp-xxhdpi/ic_allapps.png
deleted file mode 100644
index 2429656..0000000
--- a/res/drawable-sw600dp-xxhdpi/ic_allapps.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-xxhdpi/ic_allapps_pressed.png b/res/drawable-sw600dp-xxhdpi/ic_allapps_pressed.png
deleted file mode 100644
index b93a51b..0000000
--- a/res/drawable-sw600dp-xxhdpi/ic_allapps_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-xxhdpi/overscroll_glow_left.9.png b/res/drawable-sw600dp-xxhdpi/overscroll_glow_left.9.png
deleted file mode 100644
index 472c3f9..0000000
--- a/res/drawable-sw600dp-xxhdpi/overscroll_glow_left.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-xxhdpi/overscroll_glow_right.9.png b/res/drawable-sw600dp-xxhdpi/overscroll_glow_right.9.png
deleted file mode 100644
index e30cc97..0000000
--- a/res/drawable-sw600dp-xxhdpi/overscroll_glow_right.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-xxhdpi/portal_ring_inner_holo.png b/res/drawable-sw600dp-xxhdpi/portal_ring_inner_holo.png
deleted file mode 100644
index 65b2541..0000000
--- a/res/drawable-sw600dp-xxhdpi/portal_ring_inner_holo.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-xxhdpi/portal_ring_inner_nolip_holo.png b/res/drawable-sw600dp-xxhdpi/portal_ring_inner_nolip_holo.png
deleted file mode 100644
index 5068646..0000000
--- a/res/drawable-sw600dp-xxhdpi/portal_ring_inner_nolip_holo.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-xxhdpi/portal_ring_outer_holo.png b/res/drawable-sw600dp-xxhdpi/portal_ring_outer_holo.png
deleted file mode 100644
index 6628425..0000000
--- a/res/drawable-sw600dp-xxhdpi/portal_ring_outer_holo.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw720dp-hdpi/workspace_bg.9.png b/res/drawable-sw720dp-hdpi/workspace_bg.9.png
deleted file mode 100644
index 5bbfa4f..0000000
--- a/res/drawable-sw720dp-hdpi/workspace_bg.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw720dp-mdpi/workspace_bg.9.png b/res/drawable-sw720dp-mdpi/workspace_bg.9.png
deleted file mode 100644
index 2856e09..0000000
--- a/res/drawable-sw720dp-mdpi/workspace_bg.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw720dp-xhdpi/workspace_bg.9.png b/res/drawable-sw720dp-xhdpi/workspace_bg.9.png
deleted file mode 100644
index 72269f2..0000000
--- a/res/drawable-sw720dp-xhdpi/workspace_bg.9.png
+++ /dev/null
Binary files differ
diff --git a/res/layout/folder_icon.xml b/res/layout/folder_icon.xml
index 4405682..5147f99 100644
--- a/res/layout/folder_icon.xml
+++ b/res/layout/folder_icon.xml
@@ -30,5 +30,8 @@
         android:src="@drawable/portal_ring_inner_holo"/>
     <com.android.launcher3.BubbleTextView
         style="@style/WorkspaceIcon"
-        android:id="@+id/folder_icon_name" />
+        android:id="@+id/folder_icon_name"
+        android:layout_gravity="top"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
 </com.android.launcher3.FolderIcon>
diff --git a/res/layout/wallpaper_cropper.xml b/res/layout/wallpaper_cropper.xml
index 3a3d98a..abb8608 100644
--- a/res/layout/wallpaper_cropper.xml
+++ b/res/layout/wallpaper_cropper.xml
@@ -32,7 +32,7 @@
         android:visibility="invisible"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_gravity="center"
+        android:layout_centerInParent="true"
         android:indeterminate="true"
         android:indeterminateOnly="true"
         android:background="@android:color/transparent" />
diff --git a/res/layout/wallpaper_picker.xml b/res/layout/wallpaper_picker.xml
index 0492b7b..c36493d 100644
--- a/res/layout/wallpaper_picker.xml
+++ b/res/layout/wallpaper_picker.xml
@@ -27,18 +27,13 @@
         android:id="@+id/cropView"
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
-    <ImageView
-        android:id="@+id/defaultWallpaperView"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:visibility="invisible" />
     <ProgressBar
         android:id="@+id/loading"
         style="@android:style/Widget.Holo.ProgressBar.Large"
         android:visibility="invisible"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_gravity="center"
+        android:layout_centerInParent="true"
         android:indeterminate="true"
         android:indeterminateOnly="true"
         android:background="@android:color/transparent" />
diff --git a/res/values/colors.xml b/res/values/colors.xml
index dc35a3f..c073903 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -34,6 +34,7 @@
     <color name="apps_customize_icon_text_color">#FFF</color>
     <color name="wallpaper_picker_translucent_gray">#66000000</color>
     <color name="folder_items_text_color">#FF333333</color>
+    <color name="folder_items_glow_color">#FFCCCCCC</color>
     <color name="outline_color">#FFFFFFFF</color>
     
     <color name="first_run_cling_circle_background_color">#64b1ea</color>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index b4b2367..dcddc09 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -20,6 +20,7 @@
     <dimen name="dynamic_grid_search_bar_max_width">500dp</dimen>
     <dimen name="dynamic_grid_search_bar_height">48dp</dimen>
     <dimen name="dynamic_grid_page_indicator_height">24dp</dimen>
+    <dimen name="dynamic_grid_icon_drawable_padding">4dp</dimen>
 
 <!-- Wallpaper picker -->
     <dimen name="wallpaperThumbnailWidth">106.5dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f916ad8..30f4d8f 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -30,6 +30,14 @@
     <string name="folder_name"></string>
     <!-- Button label on Wallpaper picker screen; user selects this button to set a specific wallpaper -->
     <string name="wallpaper_instructions">Set wallpaper</string>
+    <!-- Error message when an image is selected as a wallpaper,
+         but the wallpaper picker cannot load it -->
+    <string name="image_load_fail">Coudn\'t load image</string>
+    <!-- Error message when an image is selected as a wallpaper,
+         but the wallpaper cropper cannot load it. The user will
+         usually see this when using another app and trying to set
+         an image as the wallpaper -->
+    <string name="wallpaper_load_fail">Couldn\'t load image as wallpaper</string>
     <!-- Shown when wallpapers are selected in Wallpaper picker -->
     <!-- String indicating how many media item(s) is(are) selected
             eg. 1 selected [CHAR LIMIT=30] -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index a1d2c5c..39e55d8 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -98,7 +98,7 @@
     <style name="WorkspaceIcon.AppsCustomize">
         <item name="android:background">@null</item>
         <item name="android:textColor">@color/apps_customize_icon_text_color</item>
-        <item name="android:drawablePadding">4dp</item>
+        <item name="android:drawablePadding">@dimen/dynamic_grid_icon_drawable_padding</item>
         <item name="android:shadowRadius">4.0</item>
         <item name="android:shadowColor">#FF000000</item>
     </style>
diff --git a/src/com/android/launcher3/AppsCustomizePagedView.java b/src/com/android/launcher3/AppsCustomizePagedView.java
index 9b35bb5..25cd9f2 100644
--- a/src/com/android/launcher3/AppsCustomizePagedView.java
+++ b/src/com/android/launcher3/AppsCustomizePagedView.java
@@ -811,13 +811,23 @@
                 !(target instanceof DeleteDropTarget))) {
             // Exit spring loaded mode if we have not successfully dropped or have not handled the
             // drop in Workspace
-            mLauncher.exitSpringLoadedDragMode();
+            mLauncher.getWorkspace().removeExtraEmptyScreen(true, new Runnable() {
+                @Override
+                public void run() {
+                    mLauncher.exitSpringLoadedDragMode();
+                    mLauncher.unlockScreenOrientation(false);
+                }
+            });
+        } else {
+            mLauncher.unlockScreenOrientation(false);
         }
-        mLauncher.unlockScreenOrientation(false);
     }
 
     @Override
     public View getContent() {
+        if (getChildCount() > 0) {
+            return getChildAt(0);
+        }
         return null;
     }
 
@@ -841,7 +851,7 @@
     public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
         mInTransition = false;
         for (AsyncTaskPageData d : mDeferredSyncWidgetPageItems) {
-            onSyncWidgetPageItems(d);
+            onSyncWidgetPageItems(d, false);
         }
         mDeferredSyncWidgetPageItems.clear();
         for (Runnable r : mDeferredPrepareLoadWidgetPreviewsTasks) {
@@ -935,12 +945,12 @@
     }
 
     public void setContentType(ContentType type) {
-        int page = getCurrentPage();
-        if (mContentType != type) {
-            page = 0;
+        // Widgets appear to be cleared every time you leave, always force invalidate for them
+        if (mContentType != type || type == ContentType.Widgets) {
+            int page = (mContentType != type) ? 0 : getCurrentPage();
+            mContentType = type;
+            invalidatePageData(page, true);
         }
-        mContentType = type;
-        invalidatePageData(page, true);
     }
 
     public ContentType getContentType() {
@@ -1110,7 +1120,7 @@
                     mRunningTasks.remove(task);
                     if (task.isCancelled()) return;
                     // do cleanup inside onSyncWidgetPageItems
-                    onSyncWidgetPageItems(data);
+                    onSyncWidgetPageItems(data, false);
                 }
             }, mWidgetPreviewLoader);
 
@@ -1226,7 +1236,7 @@
                     AsyncTaskPageData data = new AsyncTaskPageData(page, items,
                             maxPreviewWidth, maxPreviewHeight, null, null, mWidgetPreviewLoader);
                     loadWidgetPreviewsInBackground(null, data);
-                    onSyncWidgetPageItems(data);
+                    onSyncWidgetPageItems(data, immediate);
                 } else {
                     if (mInTransition) {
                         mDeferredPrepareLoadWidgetPreviewsTasks.add(this);
@@ -1265,8 +1275,8 @@
         }
     }
 
-    private void onSyncWidgetPageItems(AsyncTaskPageData data) {
-        if (mInTransition) {
+    private void onSyncWidgetPageItems(AsyncTaskPageData data, boolean immediatelySyncItems) {
+        if (!immediatelySyncItems && mInTransition) {
             mDeferredSyncWidgetPageItems.add(data);
             return;
         }
diff --git a/src/com/android/launcher3/AppsCustomizeTabHost.java b/src/com/android/launcher3/AppsCustomizeTabHost.java
index bfcf92a..697bd7e 100644
--- a/src/com/android/launcher3/AppsCustomizeTabHost.java
+++ b/src/com/android/launcher3/AppsCustomizeTabHost.java
@@ -367,6 +367,10 @@
 
     @Override
     public View getContent() {
+        View appsCustomizeContent = mAppsCustomizePane.getContent();
+        if (appsCustomizeContent != null) {
+            return appsCustomizeContent;
+        }
         return mContent;
     }
 
@@ -397,6 +401,7 @@
 
     @Override
     public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
+        mAppsCustomizePane.onLauncherTransitionStart(l, animated, toWorkspace);
         if (animated) {
             enableAndBuildHardwareLayer();
         }
@@ -407,7 +412,7 @@
 
     @Override
     public void onLauncherTransitionStep(Launcher l, float t) {
-        // Do nothing
+        mAppsCustomizePane.onLauncherTransitionStep(l, t);
     }
 
     @Override
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 8dab943..30016e5 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -87,7 +87,7 @@
         // Ensure we are using the right text size
         LauncherAppState app = LauncherAppState.getInstance();
         DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
-        setTextSize(TypedValue.COMPLEX_UNIT_SP, grid.iconTextSize);
+        setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx);
         setTextColor(getResources().getColor(R.color.workspace_icon_text_color));
     }
 
@@ -111,7 +111,7 @@
 
         setCompoundDrawables(null,
                 Utilities.createIconDrawable(b), null, null);
-        setCompoundDrawablePadding((int) ((grid.folderIconSizePx - grid.iconSizePx) / 2f));
+        setCompoundDrawablePadding(grid.iconDrawablePaddingPx);
         setText(info.title);
         setTag(info);
     }
@@ -203,6 +203,10 @@
         destCanvas.restore();
     }
 
+    public void setGlowColor(int color) {
+        mFocusedOutlineColor = mFocusedGlowColor = mPressedOutlineColor = mPressedGlowColor = color;
+    }
+
     /**
      * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
      * Responsibility for the bitmap is transferred to the caller.
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index dafb79f..bf1e9c9 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -214,7 +214,7 @@
         setAlwaysDrawnWithCacheEnabled(false);
 
         final Resources res = getResources();
-        mHotseatScale = (float) grid.hotseatIconSize / grid.iconSize;
+        mHotseatScale = (float) grid.hotseatIconSizePx / grid.iconSizePx;
 
         mNormalBackground = res.getDrawable(R.drawable.screenpanel);
         mActiveGlowBackground = res.getDrawable(R.drawable.screenpanel_hover);
diff --git a/src/com/android/launcher3/Cling.java b/src/com/android/launcher3/Cling.java
index 338b722..9974acd 100644
--- a/src/com/android/launcher3/Cling.java
+++ b/src/com/android/launcher3/Cling.java
@@ -147,7 +147,7 @@
                     pos.left + Utilities.sIconTextureWidth,
                     pos.top + Utilities.sIconTextureHeight);
             Utilities.scaleRectAboutCenter(mFocusedHotseatAppBounds,
-                    (grid.hotseatIconSize / grid.iconSize));
+                    ((float) grid.hotseatIconSizePx / grid.iconSizePx));
 
             // Set the title
             TextView v = (TextView) findViewById(R.id.focused_hotseat_app_title);
diff --git a/src/com/android/launcher3/DrawableTileSource.java b/src/com/android/launcher3/DrawableTileSource.java
new file mode 100644
index 0000000..c1f2eff
--- /dev/null
+++ b/src/com/android/launcher3/DrawableTileSource.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+package com.android.launcher3;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+
+import com.android.gallery3d.glrenderer.BasicTexture;
+import com.android.gallery3d.glrenderer.BitmapTexture;
+import com.android.photos.views.TiledImageRenderer;
+
+public class DrawableTileSource implements TiledImageRenderer.TileSource {
+    private static final int GL_SIZE_LIMIT = 2048;
+    // This must be no larger than half the size of the GL_SIZE_LIMIT
+    // due to decodePreview being allowed to be up to 2x the size of the target
+    public static final int MAX_PREVIEW_SIZE = GL_SIZE_LIMIT / 2;
+
+    private int mTileSize;
+    private int mPreviewSize;
+    private Drawable mDrawable;
+    private BitmapTexture mPreview;
+
+    public DrawableTileSource(Context context, Drawable d, int previewSize) {
+        mTileSize = TiledImageRenderer.suggestedTileSize(context);
+        mDrawable = d;
+        mPreviewSize = Math.min(previewSize, MAX_PREVIEW_SIZE);
+    }
+
+    @Override
+    public int getTileSize() {
+        return mTileSize;
+    }
+
+    @Override
+    public int getImageWidth() {
+        return mDrawable.getIntrinsicWidth();
+    }
+
+    @Override
+    public int getImageHeight() {
+        return mDrawable.getIntrinsicHeight();
+    }
+
+    @Override
+    public int getRotation() {
+        return 0;
+    }
+
+    @Override
+    public BasicTexture getPreview() {
+        if (mPreviewSize == 0) {
+            return null;
+        }
+        if (mPreview == null){
+            float width = getImageWidth();
+            float height = getImageHeight();
+            while (width > MAX_PREVIEW_SIZE || height > MAX_PREVIEW_SIZE) {
+                width /= 2;
+                height /= 2;
+            }
+            Bitmap b = Bitmap.createBitmap((int) width, (int) height, Bitmap.Config.ARGB_8888);
+            Canvas c = new Canvas(b);
+            mDrawable.setBounds(new Rect(0, 0, (int) width, (int) height));
+            mDrawable.draw(c);
+            c.setBitmap(null);
+            mPreview = new BitmapTexture(b);
+        }
+        return mPreview;
+    }
+
+    @Override
+    public Bitmap getTile(int level, int x, int y, Bitmap bitmap) {
+        int tileSize = getTileSize();
+        if (bitmap == null) {
+            bitmap = Bitmap.createBitmap(tileSize, tileSize, Bitmap.Config.ARGB_8888);
+        }
+        Canvas c = new Canvas(bitmap);
+        Rect bounds = new Rect(0, 0, getImageWidth(), getImageHeight());
+        bounds.offset(-x, -y);
+        mDrawable.setBounds(bounds);
+        mDrawable.draw(c);
+        c.setBitmap(null);
+        return bitmap;
+    }
+}
diff --git a/src/com/android/launcher3/DynamicGrid.java b/src/com/android/launcher3/DynamicGrid.java
index 4776c86..d90deca 100644
--- a/src/com/android/launcher3/DynamicGrid.java
+++ b/src/com/android/launcher3/DynamicGrid.java
@@ -23,13 +23,17 @@
 import android.content.res.Resources;
 import android.graphics.Paint;
 import android.graphics.Paint.FontMetrics;
+import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.util.DisplayMetrics;
 import android.util.TypedValue;
+import android.view.Display;
 import android.view.Gravity;
+import android.view.Surface;
 import android.view.View;
 import android.view.ViewGroup.LayoutParams;
+import android.view.WindowManager;
 import android.widget.FrameLayout;
 
 import java.util.ArrayList;
@@ -52,15 +56,20 @@
 }
 
 class DeviceProfile {
+    public static interface DeviceProfileCallbacks {
+        public void onAvailableSizeChanged(DeviceProfile grid);
+    }
+
     String name;
     float minWidthDps;
     float minHeightDps;
     float numRows;
     float numColumns;
-    float iconSize;
-    float iconTextSize;
     float numHotseatIcons;
-    float hotseatIconSize;
+    private float iconSize;
+    private float iconTextSize;
+    private int iconDrawablePaddingOriginalPx;
+    private float hotseatIconSize;
 
     boolean isLandscape;
     boolean isTablet;
@@ -75,8 +84,10 @@
     int heightPx;
     int availableWidthPx;
     int availableHeightPx;
+
     int iconSizePx;
     int iconTextSizePx;
+    int iconDrawablePaddingPx;
     int cellWidthPx;
     int cellHeightPx;
     int folderBackgroundOffset;
@@ -96,6 +107,8 @@
     int searchBarHeightPx;
     int pageIndicatorHeightPx;
 
+    private ArrayList<DeviceProfileCallbacks> mCallbacks = new ArrayList<DeviceProfileCallbacks>();
+
     DeviceProfile(String n, float w, float h, float r, float c,
                   float is, float its, float hs, float his) {
         // Ensure that we have an odd number of hotseat items (since we need to place all apps)
@@ -146,13 +159,20 @@
             points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numColumns));
         }
         numColumns = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
+        // Interpolate the hotseat length
+        points.clear();
+        for (DeviceProfile p : profiles) {
+            points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numHotseatIcons));
+        }
+        numHotseatIcons = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
+        hotseatAllAppsRank = (int) (numHotseatIcons / 2);
+
         // Interpolate the icon size
         points.clear();
         for (DeviceProfile p : profiles) {
             points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconSize));
         }
         iconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
-        iconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
 
         // Interpolate the icon text size
         points.clear();
@@ -160,14 +180,8 @@
             points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconTextSize));
         }
         iconTextSize = invDistWeightedInterpolate(minWidth, minHeight, points);
-        iconTextSizePx = DynamicGrid.pxFromSp(iconTextSize, dm);
+        iconDrawablePaddingOriginalPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
 
-        // Interpolate the hotseat size
-        points.clear();
-        for (DeviceProfile p : profiles) {
-            points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numHotseatIcons));
-        }
-        numHotseatIcons = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
         // Interpolate the hotseat icon size
         points.clear();
         for (DeviceProfile p : profiles) {
@@ -175,11 +189,91 @@
         }
         // Hotseat
         hotseatIconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
-        hotseatIconSizePx = DynamicGrid.pxFromDp(hotseatIconSize, dm);
-        hotseatAllAppsRank = (int) (numColumns / 2);
 
-        // Calculate other vars based on Configuration
-        updateFromConfiguration(resources, wPx, hPx, awPx, ahPx);
+        // Calculate the remaining vars
+        updateFromConfiguration(context, resources, wPx, hPx, awPx, ahPx);
+        updateAvailableDimensions(context);
+    }
+
+    void addCallback(DeviceProfileCallbacks cb) {
+        mCallbacks.add(cb);
+        cb.onAvailableSizeChanged(this);
+    }
+    void removeCallback(DeviceProfileCallbacks cb) {
+        mCallbacks.remove(cb);
+    }
+
+    private int getDeviceOrientation(Context context) {
+        WindowManager windowManager =  (WindowManager)
+                context.getSystemService(Context.WINDOW_SERVICE);
+        Resources resources = context.getResources();
+        DisplayMetrics dm = resources.getDisplayMetrics();
+        Configuration config = resources.getConfiguration();
+        int rotation = windowManager.getDefaultDisplay().getRotation();
+
+        boolean isLandscape = (config.orientation == Configuration.ORIENTATION_LANDSCAPE) &&
+                (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180);
+        boolean isRotatedPortrait = (config.orientation == Configuration.ORIENTATION_PORTRAIT) &&
+                (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270);
+        if (isLandscape || isRotatedPortrait) {
+            return CellLayout.LANDSCAPE;
+        } else {
+            return CellLayout.PORTRAIT;
+        }
+    }
+
+    private void updateAvailableDimensions(Context context) {
+        WindowManager windowManager =  (WindowManager)
+                context.getSystemService(Context.WINDOW_SERVICE);
+        Display display = windowManager.getDefaultDisplay();
+        Resources resources = context.getResources();
+        DisplayMetrics dm = resources.getDisplayMetrics();
+        Configuration config = resources.getConfiguration();
+
+        // There are three possible configurations that the dynamic grid accounts for, portrait,
+        // landscape with the nav bar at the bottom, and landscape with the nav bar at the side.
+        // To prevent waiting for fitSystemWindows(), we make the observation that in landscape,
+        // the height is the smallest height (either with the nav bar at the bottom or to the
+        // side) and otherwise, the height is simply the largest possible height for a portrait
+        // device.
+        Point size = new Point();
+        Point smallestSize = new Point();
+        Point largestSize = new Point();
+        display.getSize(size);
+        display.getCurrentSizeRange(smallestSize, largestSize);
+        availableWidthPx = size.x;
+        if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+            availableHeightPx = smallestSize.y;
+        } else {
+            availableHeightPx = largestSize.y;
+        }
+
+        // Check to see if the icons fit in the new available height.  If not, then we need to
+        // shrink the icon size.
+        Rect workspacePadding = getWorkspacePadding();
+        float scale = 1f;
+        int drawablePadding = iconDrawablePaddingOriginalPx;
+        updateIconSize(1f, drawablePadding, resources, dm);
+        float usedHeight = (cellHeightPx * numRows);
+        int maxHeight = (availableHeightPx - workspacePadding.top - workspacePadding.bottom);
+        if (usedHeight > maxHeight) {
+            scale = maxHeight / usedHeight;
+            drawablePadding = 0;
+        }
+        updateIconSize(scale, drawablePadding, resources, dm);
+
+        // Make the callbacks
+        for (DeviceProfileCallbacks cb : mCallbacks) {
+            cb.onAvailableSizeChanged(this);
+        }
+    }
+
+    private void updateIconSize(float scale, int drawablePadding, Resources resources,
+                                DisplayMetrics dm) {
+        iconSizePx = (int) (DynamicGrid.pxFromDp(iconSize, dm) * scale);
+        iconTextSizePx = (int) (DynamicGrid.pxFromSp(iconTextSize, dm) * scale);
+        iconDrawablePaddingPx = drawablePadding;
+        hotseatIconSizePx = (int) (DynamicGrid.pxFromDp(hotseatIconSize, dm) * scale);
 
         // Search Bar
         searchBarSpaceMaxWidthPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width);
@@ -192,23 +286,7 @@
         textPaint.setTextSize(iconTextSizePx);
         FontMetrics fm = textPaint.getFontMetrics();
         cellWidthPx = iconSizePx;
-        cellHeightPx = iconSizePx + (int) Math.ceil(fm.bottom - fm.top);
-
-        // At this point, if the cells do not fit into the available height, then we need
-        // to shrink the icon size
-        /*
-        Rect padding = getWorkspacePadding(isLandscape ?
-                CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
-        int h = (int) (numRows * cellHeightPx) + padding.top + padding.bottom;
-        if (h > availableHeightPx) {
-            float delta = h - availableHeightPx;
-            int deltaPx = (int) Math.ceil(delta / numRows);
-            iconSizePx -= deltaPx;
-            iconSize = DynamicGrid.dpiFromPx(iconSizePx, dm);
-            cellWidthPx = iconSizePx;
-            cellHeightPx = iconSizePx + (int) Math.ceil(fm.bottom - fm.top);
-        }
-        */
+        cellHeightPx = iconSizePx + iconDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top);
 
         // Hotseat
         hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
@@ -217,12 +295,26 @@
 
         // Folder
         folderCellWidthPx = cellWidthPx + 3 * edgeMarginPx;
-        folderCellHeightPx = cellHeightPx + (int) ((3f/2f) * edgeMarginPx);
+        folderCellHeightPx = cellHeightPx + edgeMarginPx;
         folderBackgroundOffset = -edgeMarginPx;
         folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
+
+        // All Apps
+        Rect padding = getWorkspacePadding(isLandscape ?
+                CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
+        int pageIndicatorOffset =
+                resources.getDimensionPixelSize(R.dimen.apps_customize_page_indicator_offset);
+        if (isLandscape) {
+            allAppsNumRows = (availableHeightPx - pageIndicatorOffset - 4 * edgeMarginPx) /
+                    (iconSizePx + iconTextSizePx + 2 * edgeMarginPx);
+        } else {
+            allAppsNumRows = (int) numRows + 1;
+        }
+        allAppsNumCols = (availableWidthPx - padding.left - padding.right - 2 * edgeMarginPx) /
+                (iconSizePx + 2 * edgeMarginPx);
     }
 
-    void updateFromConfiguration(Resources resources, int wPx, int hPx,
+    void updateFromConfiguration(Context context, Resources resources, int wPx, int hPx,
                                  int awPx, int ahPx) {
         isLandscape = (resources.getConfiguration().orientation ==
                 Configuration.ORIENTATION_LANDSCAPE);
@@ -233,18 +325,7 @@
         availableWidthPx = awPx;
         availableHeightPx = ahPx;
 
-        Rect padding = getWorkspacePadding(isLandscape ?
-                CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
-        int pageIndicatorOffset =
-            resources.getDimensionPixelSize(R.dimen.apps_customize_page_indicator_offset);
-        if (isLandscape) {
-            allAppsNumRows = (availableHeightPx - pageIndicatorOffset - 4 * edgeMarginPx) /
-                    (iconSizePx + iconTextSizePx + 2 * edgeMarginPx);
-        } else {
-            allAppsNumRows = (int) numRows + 1;
-        }
-        allAppsNumCols = (availableWidthPx - padding.left - padding.right - 2 * edgeMarginPx) /
-                (iconSizePx + 2 * edgeMarginPx);
+        updateAvailableDimensions(context);
     }
 
     private float dist(PointF p0, PointF p1) {
@@ -298,6 +379,9 @@
         return sum;
     }
 
+    Rect getWorkspacePadding() {
+        return getWorkspacePadding(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
+    }
     Rect getWorkspacePadding(int orientation) {
         Rect padding = new Rect();
         if (orientation == CellLayout.LANDSCAPE &&
@@ -431,8 +515,7 @@
             lp.gravity = Gravity.RIGHT;
             lp.width = hotseatBarHeightPx;
             lp.height = LayoutParams.MATCH_PARENT;
-            hotseat.setPadding(0, 2 * edgeMarginPx,
-                    2 * edgeMarginPx, 2 * edgeMarginPx);
+            hotseat.findViewById(R.id.layout).setPadding(0, 2 * edgeMarginPx, 0, 2 * edgeMarginPx);
         } else if (isTablet()) {
             // Pad the hotseat with the grid gap calculated above
             int gridGap = (int) ((widthPx - 2 * edgeMarginPx -
@@ -553,7 +636,7 @@
                 "Wd: " + mProfile.minWidthDps + ", Hd: " + mProfile.minHeightDps +
                 ", W: " + mProfile.widthPx + ", H: " + mProfile.heightPx +
                 " [r: " + mProfile.numRows + ", c: " + mProfile.numColumns +
-                ", is: " + mProfile.iconSizePx + ", its: " + mProfile.iconTextSize +
+                ", is: " + mProfile.iconSizePx + ", its: " + mProfile.iconTextSizePx +
                 ", cw: " + mProfile.cellWidthPx + ", ch: " + mProfile.cellHeightPx +
                 ", hc: " + mProfile.numHotseatIcons + ", his: " + mProfile.hotseatIconSizePx + "]";
     }
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index 14760c7..bce6707 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -67,6 +67,7 @@
 
     public void setFilterBitmap(boolean filterBitmap) {
         mPaint.setFilterBitmap(filterBitmap);
+        mPaint.setAntiAlias(filterBitmap);
     }
 
     public int getAlpha() {
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index 69d9a3d..bd61010 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -538,6 +538,7 @@
         textView.setTag(item);
         textView.setTextColor(getResources().getColor(R.color.folder_items_text_color));
         textView.setShadowsEnabled(false);
+        textView.setGlowColor(getResources().getColor(R.color.folder_items_glow_color));
 
         textView.setOnClickListener(this);
         textView.setOnLongClickListener(this);
@@ -956,9 +957,7 @@
         Rect workspacePadding = grid.getWorkspacePadding(grid.isLandscape ?
                 CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
         int maxContentAreaHeight = grid.availableHeightPx -
-                4 * grid.edgeMarginPx -
                 workspacePadding.top - workspacePadding.bottom -
-                getPaddingTop() - getPaddingBottom() -
                 mFolderNameHeight;
         return Math.min(maxContentAreaHeight,
                 mContent.getDesiredHeight());
diff --git a/src/com/android/launcher3/FolderIcon.java b/src/com/android/launcher3/FolderIcon.java
index cd1ff2c..5e461a7 100644
--- a/src/com/android/launcher3/FolderIcon.java
+++ b/src/com/android/launcher3/FolderIcon.java
@@ -37,7 +37,7 @@
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.DecelerateInterpolator;
 import android.widget.ImageView;
-import android.widget.LinearLayout;
+import android.widget.FrameLayout;
 import android.widget.TextView;
 
 import com.android.launcher3.R;
@@ -49,7 +49,7 @@
 /**
  * An icon that can appear on in the workspace representing an {@link UserFolder}.
  */
-public class FolderIcon extends LinearLayout implements FolderListener {
+public class FolderIcon extends FrameLayout implements FolderListener {
     private Launcher mLauncher;
     private Folder mFolder;
     private FolderInfo mInfo;
@@ -134,17 +134,20 @@
                     "INITIAL_ITEM_ANIMATION_DURATION, as sequencing of adding first two items " +
                     "is dependent on this");
         }
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
 
         FolderIcon icon = (FolderIcon) LayoutInflater.from(launcher).inflate(resId, group, false);
         icon.setClipToPadding(false);
         icon.mFolderName = (BubbleTextView) icon.findViewById(R.id.folder_icon_name);
         icon.mFolderName.setText(folderInfo.title);
-        icon.mPreviewBackground = (ImageView) icon.findViewById(R.id.preview_background);
-        LauncherAppState app = LauncherAppState.getInstance();
-        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+        icon.mFolderName.setCompoundDrawablePadding(0);
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) icon.mFolderName.getLayoutParams();
+        lp.topMargin = grid.iconSizePx + grid.iconDrawablePaddingPx;
+
         // Offset the preview background to center this view accordingly
-        LinearLayout.LayoutParams lp =
-                (LinearLayout.LayoutParams) icon.mPreviewBackground.getLayoutParams();
+        icon.mPreviewBackground = (ImageView) icon.findViewById(R.id.preview_background);
+        lp = (FrameLayout.LayoutParams) icon.mPreviewBackground.getLayoutParams();
         lp.topMargin = grid.folderBackgroundOffset;
         lp.width = grid.folderIconSizePx;
         lp.height = grid.folderIconSizePx;
@@ -537,12 +540,10 @@
         if (d != null) {
             mOldBounds.set(d.getBounds());
             d.setBounds(0, 0, mIntrinsicIconSize, mIntrinsicIconSize);
-            d.setFilterBitmap(true);
             d.setColorFilter(Color.argb(params.overlayAlpha, 255, 255, 255),
                     PorterDuff.Mode.SRC_ATOP);
             d.draw(canvas);
             d.clearColorFilter();
-            d.setFilterBitmap(false);
             d.setBounds(mOldBounds);
         }
         canvas.restore();
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 543b8ee..a55fce0 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -156,7 +156,7 @@
             Iterator<Entry<ComponentName, CacheEntry>> it = mCache.entrySet().iterator();
             while (it.hasNext()) {
                 final CacheEntry e = it.next().getValue();
-                if (e.icon.getWidth() != grid.iconSizePx || e.icon.getHeight() != grid.iconSizePx) {
+                if (e.icon.getWidth() < grid.iconSizePx || e.icon.getHeight() < grid.iconSizePx) {
                     it.remove();
                 }
             }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index af58f79..f721571 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -197,8 +197,8 @@
     private AnimatorSet mStateAnimation;
 
     static final int APPWIDGET_HOST_ID = 1024;
-    private static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 300;
-    private static final int EXIT_SPRINGLOADED_MODE_LONG_TIMEOUT = 600;
+    public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 300;
+    private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
     private static final int SHOW_CLING_DURATION = 250;
     private static final int DISMISS_CLING_DURATION = 200;
 
@@ -305,6 +305,7 @@
     private Drawable mWorkspaceBackgroundDrawable;
 
     private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<Integer>();
+    private static final boolean DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE = false;
 
     static final ArrayList<String> sDumpLogs = new ArrayList<String>();
     static Date sDateStamp = new Date();
@@ -390,6 +391,7 @@
         display.getRealSize(realSize);
         DisplayMetrics dm = new DisplayMetrics();
         display.getMetrics(dm);
+
         // Lazy-initialize the dynamic grid
         DeviceProfile grid = app.initDynamicGrid(this,
                 Math.min(smallestSize.x, smallestSize.y),
@@ -437,18 +439,12 @@
         mSavedState = savedInstanceState;
         restoreState(mSavedState);
 
-        // Update customization drawer _after_ restoring the states
-        if (mAppsCustomizeContent != null) {
-            mAppsCustomizeContent.onPackagesUpdated(
-                LauncherModel.getSortedWidgetsAndShortcuts(this));
-        }
-
         if (PROFILE_STARTUP) {
             android.os.Debug.stopMethodTracing();
         }
 
         if (!mRestoring) {
-            if (sPausedFromUserAction) {
+            if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE || sPausedFromUserAction) {
                 // If the user leaves launcher, then we should just load items asynchronously when
                 // they return.
                 mModel.startLoader(true, -1);
@@ -709,13 +705,24 @@
         // Reset the startActivity waiting flag
         mWaitingForResult = false;
 
+        Runnable exitSpringLoaded = new Runnable() {
+            @Override
+            public void run() {
+                exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED),
+                        EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
+            }
+        };
+
         if (requestCode == REQUEST_BIND_APPWIDGET) {
-            int appWidgetId = data != null ?
+            final int appWidgetId = data != null ?
                     data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
             if (resultCode == RESULT_CANCELED) {
                 completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId);
+                mWorkspace.removeExtraEmptyScreen(true, exitSpringLoaded,
+                        ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
             } else if (resultCode == RESULT_OK) {
-                addAppWidgetImpl(appWidgetId, mPendingAddInfo, null, mPendingAddWidgetInfo);
+                addAppWidgetImpl(appWidgetId, mPendingAddInfo, null,
+                        mPendingAddWidgetInfo, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
             }
             return;
         } else if (requestCode == REQUEST_PICK_WALLPAPER) {
@@ -725,22 +732,37 @@
             return;
         }
 
-        boolean delayExitSpringLoadedMode = false;
         boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET ||
                 requestCode == REQUEST_CREATE_APPWIDGET);
 
         // We have special handling for widgets
         if (isWidgetDrop) {
-            int appWidgetId = data != null ?
+            final int appWidgetId = data != null ?
                     data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
+            final int result;
+            final Runnable onComplete;
             if (appWidgetId < 0) {
                 Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not returned from the \\" +
                         "widget configuration activity.");
-                completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId);
-                mWorkspace.stripEmptyScreens();
+                result = RESULT_CANCELED;
+                completeTwoStageWidgetDrop(result, appWidgetId);
+                onComplete = new Runnable() {
+                    @Override
+                    public void run() {
+                        exitSpringLoadedDragModeDelayed(false, 0, null);
+                    }
+                };
             } else {
-                completeTwoStageWidgetDrop(resultCode, appWidgetId);
+                result = resultCode;
+                onComplete = new Runnable() {
+                    @Override
+                    public void run() {
+                        completeTwoStageWidgetDrop(result, appWidgetId);
+                    }
+                };
             }
+            mWorkspace.removeExtraEmptyScreen(true, onComplete, ON_ACTIVITY_RESULT_ANIMATION_DELAY,
+                    false);
             return;
         }
 
@@ -760,15 +782,15 @@
             if (isWorkspaceLocked()) {
                 sPendingAddList.add(args);
             } else {
-                delayExitSpringLoadedMode = completeAdd(args);
+                completeAdd(args);
             }
+            mWorkspace.removeExtraEmptyScreen(true, exitSpringLoaded,
+                    ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
         } else if (resultCode == RESULT_CANCELED) {
-            mWorkspace.stripEmptyScreens();
+            mWorkspace.removeExtraEmptyScreen(true, exitSpringLoaded,
+                    ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
         }
         mDragLayer.clearAnimatedView();
-        // Exit spring loaded mode if necessary after cancelling the configuration of a widget
-        exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), delayExitSpringLoadedMode,
-                null);
     }
 
     private void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) {
@@ -788,25 +810,18 @@
                 public void run() {
                     completeAddAppWidget(appWidgetId, mPendingAddInfo.container,
                             mPendingAddInfo.screenId, layout, null);
-                    exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), false,
-                            null);
+                    exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED),
+                            EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
                 }
             };
         } else if (resultCode == RESULT_CANCELED) {
             animationType = Workspace.CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION;
-            onCompleteRunnable = new Runnable() {
-                @Override
-                public void run() {
-                    exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), false,
-                            null);
-                }
-            };
         }
         if (mDragLayer.getAnimatedView() != null) {
             mWorkspace.animateWidgetDrop(mPendingAddInfo, cellLayout,
                     (DragView) mDragLayer.getAnimatedView(), onCompleteRunnable,
                     animationType, boundWidget, true);
-        } else {
+        } else if (onCompleteRunnable != null) {
             // The animated view may be null in the case of a rotation during widget configuration
             onCompleteRunnable.run();
         }
@@ -1900,8 +1915,14 @@
         mPendingAddInfo.dropPos = null;
     }
 
-    void addAppWidgetImpl(final int appWidgetId, ItemInfo info, AppWidgetHostView boundWidget,
-            AppWidgetProviderInfo appWidgetInfo) {
+    void addAppWidgetImpl(final int appWidgetId, final ItemInfo info,
+            final AppWidgetHostView boundWidget, final AppWidgetProviderInfo appWidgetInfo) {
+        addAppWidgetImpl(appWidgetId, info, boundWidget, appWidgetInfo, 0);
+    }
+
+    void addAppWidgetImpl(final int appWidgetId, final ItemInfo info,
+            final AppWidgetHostView boundWidget, final AppWidgetProviderInfo appWidgetInfo, int
+            delay) {
         if (appWidgetInfo.configure != null) {
             mPendingAddWidgetInfo = appWidgetInfo;
 
@@ -1912,10 +1933,17 @@
             Utilities.startActivityForResultSafely(this, intent, REQUEST_CREATE_APPWIDGET);
         } else {
             // Otherwise just add it
+            Runnable onComplete = new Runnable() {
+                @Override
+                public void run() {
+                    // Exit spring loaded mode if necessary after adding the widget
+                    exitSpringLoadedDragModeDelayed(true, EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT,
+                            null);
+                }
+            };
             completeAddAppWidget(appWidgetId, info.container, info.screenId, boundWidget,
                     appWidgetInfo);
-            // Exit spring loaded mode if necessary after adding the widget
-            exitSpringLoadedDragModeDelayed(true, false, null);
+            mWorkspace.removeExtraEmptyScreen(true, onComplete, delay, false);
         }
     }
 
@@ -2979,7 +3007,7 @@
 
             dispatchOnLauncherTransitionPrepare(fromView, animated, true);
             dispatchOnLauncherTransitionPrepare(toView, animated, true);
-            mAppsCustomizeContent.pauseScrolling();
+            mAppsCustomizeContent.stopScrolling();
 
             mStateAnimation.addListener(new AnimatorListenerAdapter() {
                 @Override
@@ -2991,7 +3019,6 @@
                         onCompleteRunnable.run();
                     }
                     mAppsCustomizeContent.updateCurrentPageScroll();
-                    mAppsCustomizeContent.resumeScrolling();
                 }
             });
 
@@ -3104,7 +3131,7 @@
         }
     }
 
-    void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, boolean extendedDelay,
+    void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, int delay,
             final Runnable onCompleteRunnable) {
         if (mState != State.APPS_CUSTOMIZE_SPRING_LOADED) return;
 
@@ -3121,9 +3148,8 @@
                     exitSpringLoadedDragMode();
                 }
             }
-        }, (extendedDelay ?
-                EXIT_SPRINGLOADED_MODE_LONG_TIMEOUT :
-                EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT));
+        }, delay);
+
     }
 
     void exitSpringLoadedDragMode() {
@@ -3662,7 +3688,7 @@
         }
 
         // Remove the extra empty screen
-        mWorkspace.removeExtraEmptyScreen();
+        mWorkspace.removeExtraEmptyScreen(false, null);
 
         if (!AppsCustomizePagedView.DISABLE_ALL_APPS &&
                 addedApps != null && mAppsCustomizeContent != null) {
@@ -3932,6 +3958,8 @@
         } else {
             if (mAppsCustomizeContent != null) {
                 mAppsCustomizeContent.setApps(apps);
+                mAppsCustomizeContent.onPackagesUpdated(
+                        LauncherModel.getSortedWidgetsAndShortcuts(this));
             }
         }
     }
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index 01f72a7..e6c220b 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -25,11 +25,13 @@
 import android.view.ViewTreeObserver;
 
 import java.util.HashSet;
+import java.util.WeakHashMap;
 
 public class LauncherAnimUtils {
-    static HashSet<Animator> sAnimators = new HashSet<Animator>();
+    static WeakHashMap<Animator, Object> sAnimators = new WeakHashMap<Animator, Object>();
     static Animator.AnimatorListener sEndAnimListener = new Animator.AnimatorListener() {
         public void onAnimationStart(Animator animation) {
+            sAnimators.put(animation, null);
         }
 
         public void onAnimationRepeat(Animator animation) {
@@ -45,7 +47,6 @@
     };
 
     public static void cancelOnDestroyActivity(Animator a) {
-        sAnimators.add(a);
         a.addListener(sEndAnimListener);
     }
 
@@ -74,13 +75,12 @@
     }
 
     public static void onDestroyActivity() {
-        HashSet<Animator> animators = new HashSet<Animator>(sAnimators);
+        HashSet<Animator> animators = new HashSet<Animator>(sAnimators.keySet());
         for (Animator a : animators) {
             if (a.isRunning()) {
                 a.cancel();
-            } else {
-                sAnimators.remove(a);
             }
+            sAnimators.remove(a);
         }
     }
 
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index a255b89..fe2b43f 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -28,7 +28,7 @@
 
 import java.lang.ref.WeakReference;
 
-public class LauncherAppState {
+public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks {
     private static final String TAG = "LauncherAppState";
     private static final String SHARED_PREFERENCES_KEY = "com.android.launcher3.prefs";
 
@@ -182,12 +182,12 @@
                     context.getResources(),
                     minWidth, minHeight, width, height,
                     availableWidth, availableHeight);
+            mDynamicGrid.getDeviceProfile().addCallback(this);
         }
 
         // Update the icon size
         DeviceProfile grid = mDynamicGrid.getDeviceProfile();
-        Utilities.setIconSize(grid.iconSizePx);
-        grid.updateFromConfiguration(context.getResources(), width, height,
+        grid.updateFromConfiguration(context, context.getResources(), width, height,
                 availableWidth, availableHeight);
         return grid;
     }
@@ -216,4 +216,9 @@
     public int getLongPressTimeout() {
         return mLongPressTimeout;
     }
+
+    @Override
+    public void onAvailableSizeChanged(DeviceProfile grid) {
+        Utilities.setIconSize(grid.iconSizePx);
+    }
 }
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index d0f6770..e992706 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -1046,7 +1046,6 @@
                             // recursively load some more favorites, why not?
                             i += loadFavorites(db, resId);
                             added = false;
-                            mMaxItemId = -1;
                         } else {
                             Log.w(TAG, String.format("Skipping <include workspace=0x%08x>", resId));
                         }
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index e724063..9b891e4 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -157,7 +157,6 @@
     protected int mTouchSlop;
     private int mPagingTouchSlop;
     private int mMaximumVelocity;
-    protected int mPageSpacing;
     protected int mPageLayoutPaddingTop;
     protected int mPageLayoutPaddingBottom;
     protected int mPageLayoutPaddingLeft;
@@ -260,9 +259,6 @@
     // Drop to delete
     private View mDeleteDropTarget;
 
-    private boolean mAutoComputePageSpacing = false;
-    private boolean mRecomputePageSpacing = false;
-
     // Bouncer
     private boolean mTopAlignPageWhenShrinkingForBouncer = false;
 
@@ -285,10 +281,7 @@
 
         TypedArray a = context.obtainStyledAttributes(attrs,
                 R.styleable.PagedView, defStyle, 0);
-        setPageSpacing(a.getDimensionPixelSize(R.styleable.PagedView_pageSpacing, 0));
-        if (mPageSpacing < 0) {
-            mAutoComputePageSpacing = mRecomputePageSpacing = true;
-        }
+
         mPageLayoutPaddingTop = a.getDimensionPixelSize(
                 R.styleable.PagedView_pageLayoutPaddingTop, 0);
         mPageLayoutPaddingBottom = a.getDimensionPixelSize(
@@ -513,33 +506,41 @@
         }
         scrollTo(newX, 0);
         mScroller.setFinalX(newX);
-        mScroller.forceFinished(true);
+        forceFinishScroller();
     }
 
     /**
      * Called during AllApps/Home transitions to avoid unnecessary work. When that other animation
-     * ends, {@link #resumeScrolling()} should be called, along with
-     * {@link #updateCurrentPageScroll()} to correctly set the final state and re-enable scrolling.
+     * {@link #updateCurrentPageScroll()} should be called, to correctly set the final state and
+     * re-enable scrolling.
      */
-    void pauseScrolling() {
-        mScroller.forceFinished(true);
+    void stopScrolling() {
+        mCurrentPage = getNextPage();
+        forceFinishScroller();
     }
 
-    /**
-     * Enables scrolling again.
-     * @see #pauseScrolling()
-     */
-    void resumeScrolling() {
+    private void abortScrollerAnimation(boolean resetNextPage) {
+        mScroller.abortAnimation();
+        // We need to clean up the next page here to avoid computeScrollHelper from
+        // updating current page on the pass.
+        if (resetNextPage) {
+            mNextPage = INVALID_PAGE;
+        }
     }
+
+    private void forceFinishScroller() {
+        mScroller.forceFinished(true);
+        // We need to clean up the next page here to avoid computeScrollHelper from
+        // updating current page on the pass.
+        mNextPage = INVALID_PAGE;
+    }
+
     /**
      * Sets the current page.
      */
     void setCurrentPage(int currentPage) {
         if (!mScroller.isFinished()) {
-            mScroller.abortAnimation();
-            // We need to clean up the next page here to avoid computeScrollHelper from
-            // updating current page on the pass.
-            mNextPage = INVALID_PAGE;
+            abortScrollerAnimation(true);
         }
         // don't introduce any checks like mCurrentPage == currentPage here-- if we change the
         // the default
@@ -875,26 +876,6 @@
             }
         }
         setMeasuredDimension(scaledWidthSize, scaledHeightSize);
-
-        if (childCount > 0) {
-            // Calculate the variable page spacing if necessary
-            if (mAutoComputePageSpacing && mRecomputePageSpacing) {
-                // The gap between pages in the PagedView should be equal to the gap from the page
-                // to the edge of the screen (so it is not visible in the current screen).  To
-                // account for unequal padding on each side of the paged view, we take the maximum
-                // of the left/right gap and use that as the gap between each page.
-                int offset = (getViewportWidth() - getChildWidth(0)) / 2;
-                int spacing = Math.max(offset, widthSize - offset -
-                        getChildAt(0).getMeasuredWidth());
-                setPageSpacing(spacing);
-                mRecomputePageSpacing = false;
-            }
-        }
-    }
-
-    public void setPageSpacing(int pageSpacing) {
-        mPageSpacing = pageSpacing;
-        requestLayout();
     }
 
     @Override
@@ -1029,7 +1010,6 @@
         // This ensures that when children are added, they get the correct transforms / alphas
         // in accordance with any scroll effects.
         mForceScreenScrolled = true;
-        mRecomputePageSpacing = true;
         updateFreescrollBounds();
         invalidate();
     }
@@ -1301,9 +1281,9 @@
         int offset = (getViewportWidth() - getChildWidth(mCurrentPage)) / 2;
         if (isLayoutRtl()) {
             return (x > (getViewportOffsetX() + getViewportWidth() -
-                    offset + mPageSpacing));
+                    offset));
         }
-        return (x < getViewportOffsetX() + offset - mPageSpacing);
+        return (x < getViewportOffsetX() + offset);
     }
 
     /**
@@ -1312,10 +1292,10 @@
     protected boolean hitsNextPage(float x, float y) {
         int offset = (getViewportWidth() - getChildWidth(mCurrentPage)) / 2;
         if (isLayoutRtl()) {
-            return (x < getViewportOffsetX() + offset - mPageSpacing);
+            return (x < getViewportOffsetX() + offset);
         }
         return  (x > (getViewportOffsetX() + getViewportWidth() -
-                offset + mPageSpacing));
+                offset));
     }
 
     /** Returns whether x and y originated within the buffered viewport */
@@ -1394,7 +1374,7 @@
                 final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop);
                 if (finishedScrolling) {
                     mTouchState = TOUCH_STATE_REST;
-                    mScroller.abortAnimation();
+                    abortScrollerAnimation(false);
                 } else {
                     if (isTouchPointInViewportWithBuffer((int) mDownMotionX, (int) mDownMotionY)) {
                         mTouchState = TOUCH_STATE_SCROLLING;
@@ -1506,7 +1486,8 @@
     protected float getScrollProgress(int screenCenter, View v, int page) {
         final int halfScreenSize = getViewportWidth() / 2;
 
-        int totalDistance = v.getMeasuredWidth() + mPageSpacing;
+        int offset = (getViewportWidth() - getChildWidth(page)) / 2;
+        int totalDistance = v.getMeasuredWidth() + offset;
         int delta = screenCenter - (getScrollForPage(page) + halfScreenSize);
 
         float scrollProgress = delta / (totalDistance * 1.0f);
@@ -1692,7 +1673,7 @@
              * will be false if being flinged.
              */
             if (!mScroller.isFinished()) {
-                mScroller.abortAnimation();
+                abortScrollerAnimation(false);
             }
 
             // Remember where the motion event started
@@ -1890,7 +1871,7 @@
                     }
                 } else {
                     if (!mScroller.isFinished()) {
-                        mScroller.abortAnimation();
+                        abortScrollerAnimation(true);
                     }
 
                     float scaleX = getScaleX();
@@ -2354,8 +2335,7 @@
 
         if (mContentIsRefreshable) {
             // Force all scrolling-related behavior to end
-            mScroller.forceFinished(true);
-            mNextPage = INVALID_PAGE;
+            forceFinishScroller();
 
             // Update all the pages
             syncPages();
@@ -2595,9 +2575,10 @@
                     int newX = 0;
                     if (slideFromLeft) {
                         if (i == 0) {
+                            int pageSpace = (getViewportWidth() - getChildWidth(i)) / 2;
                             // Simulate the page being offscreen with the page spacing
                             oldX = getViewportOffsetX() + getChildOffset(i) - getChildWidth(i)
-                                    - mPageSpacing;
+                                    - pageSpace;
                         } else {
                             oldX = getViewportOffsetX() + getChildOffset(i - 1);
                         }
diff --git a/src/com/android/launcher3/PagedViewIcon.java b/src/com/android/launcher3/PagedViewIcon.java
index 8bfe42d..865c0b2 100644
--- a/src/com/android/launcher3/PagedViewIcon.java
+++ b/src/com/android/launcher3/PagedViewIcon.java
@@ -62,15 +62,19 @@
         // Ensure we are using the right text size
         LauncherAppState app = LauncherAppState.getInstance();
         DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
-        setTextSize(TypedValue.COMPLEX_UNIT_SP, grid.iconTextSize);
+        setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx);
     }
 
     public void applyFromApplicationInfo(AppInfo info, boolean scaleUp,
             PagedViewIcon.PressedCallback cb) {
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+
         mIcon = info.iconBitmap;
         mPressedCallback = cb;
         setCompoundDrawables(null, Utilities.createIconDrawable(mIcon),
                 null, null);
+        setCompoundDrawablePadding(grid.iconDrawablePaddingPx);
         setText(info.title);
         setTag(info);
     }
diff --git a/src/com/android/launcher3/PagedViewWidget.java b/src/com/android/launcher3/PagedViewWidget.java
index 45320a4..d2f897a 100644
--- a/src/com/android/launcher3/PagedViewWidget.java
+++ b/src/com/android/launcher3/PagedViewWidget.java
@@ -84,11 +84,11 @@
         DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
         TextView name = (TextView) findViewById(R.id.widget_name);
         if (name != null) {
-            name.setTextSize(TypedValue.COMPLEX_UNIT_SP, grid.iconTextSize);
+            name.setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx);
         }
         TextView dims = (TextView) findViewById(R.id.widget_dims);
         if (dims != null) {
-            dims.setTextSize(TypedValue.COMPLEX_UNIT_SP, grid.iconTextSize);
+            dims.setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx);
         }
     }
 
diff --git a/src/com/android/launcher3/SavedWallpaperImages.java b/src/com/android/launcher3/SavedWallpaperImages.java
index c19692b..58add70 100644
--- a/src/com/android/launcher3/SavedWallpaperImages.java
+++ b/src/com/android/launcher3/SavedWallpaperImages.java
@@ -60,13 +60,9 @@
         public void onClick(WallpaperPickerActivity a) {
             String imageFilename = a.getSavedImages().getImageFilename(mDbId);
             File file = new File(a.getFilesDir(), imageFilename);
-            CropView v = a.getCropView();
-            int rotation = WallpaperCropActivity.getRotationFromExif(file.getAbsolutePath());
-            a.getDefaultWallpaperView().setVisibility(View.INVISIBLE);
-            v.setTileSource(
-                    new BitmapRegionTileSource(a, file.getAbsolutePath(), 1024, rotation), null);
-            v.moveToLeft();
-            v.setTouchEnabled(false);
+            BitmapRegionTileSource.FilePathBitmapSource bitmapSource =
+                    new BitmapRegionTileSource.FilePathBitmapSource(file.getAbsolutePath(), 1024);
+            a.setCropViewTileSource(bitmapSource, false, true, null);
         }
         @Override
         public void onSave(WallpaperPickerActivity a) {
diff --git a/src/com/android/launcher3/SmoothPagedView.java b/src/com/android/launcher3/SmoothPagedView.java
index a45dbbf..64dcb34 100644
--- a/src/com/android/launcher3/SmoothPagedView.java
+++ b/src/com/android/launcher3/SmoothPagedView.java
@@ -52,8 +52,6 @@
         }
 
         public float getInterpolation(float t) {
-            // _o(t) = t * t * ((tension + 1) * t + tension)
-            // o(t) = _o(t - 1) + 1
             t -= 1.0f;
             return t * t * ((mTension + 1) * t + mTension) + 1.0f;
         }
diff --git a/src/com/android/launcher3/WallpaperCropActivity.java b/src/com/android/launcher3/WallpaperCropActivity.java
index 30ec340..f9aec2b 100644
--- a/src/com/android/launcher3/WallpaperCropActivity.java
+++ b/src/com/android/launcher3/WallpaperCropActivity.java
@@ -37,15 +37,16 @@
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
-import android.util.FloatMath;
 import android.util.Log;
 import android.view.Display;
 import android.view.View;
 import android.view.WindowManager;
+import android.widget.Toast;
 
 import com.android.gallery3d.common.Utils;
 import com.android.gallery3d.exif.ExifInterface;
 import com.android.photos.BitmapRegionTileSource;
+import com.android.photos.BitmapRegionTileSource.BitmapSource;
 
 import java.io.BufferedInputStream;
 import java.io.ByteArrayInputStream;
@@ -96,9 +97,6 @@
             return;
         }
 
-        int rotation = getRotationFromExif(this, imageUri);
-        mCropView.setTileSource(new BitmapRegionTileSource(this, imageUri, 1024, rotation), null);
-        mCropView.setTouchEnabled(true);
         // Action bar
         // Show the custom action bar view
         final ActionBar actionBar = getActionBar();
@@ -111,6 +109,63 @@
                         cropImageAndSetWallpaper(imageUri, null, finishActivityWhenDone);
                     }
                 });
+
+        // Load image in background
+        final BitmapRegionTileSource.UriBitmapSource bitmapSource =
+                new BitmapRegionTileSource.UriBitmapSource(this, imageUri, 1024);
+        Runnable onLoad = new Runnable() {
+            public void run() {
+                if (bitmapSource.getLoadingState() != BitmapSource.State.LOADED) {
+                    Toast.makeText(WallpaperCropActivity.this,
+                            getString(R.string.wallpaper_load_fail),
+                            Toast.LENGTH_LONG).show();
+                    finish();
+                }
+            }
+        };
+        setCropViewTileSource(bitmapSource, true, false, onLoad);
+    }
+
+    public void setCropViewTileSource(
+            final BitmapRegionTileSource.BitmapSource bitmapSource, final boolean touchEnabled,
+            final boolean moveToLeft, final Runnable postExecute) {
+        final Context context = WallpaperCropActivity.this;
+        final View progressView = findViewById(R.id.loading);
+        final AsyncTask<Void, Void, Void> loadBitmapTask = new AsyncTask<Void, Void, Void>() {
+            protected Void doInBackground(Void...args) {
+                if (!isCancelled()) {
+                    bitmapSource.loadInBackground();
+                }
+                return null;
+            }
+            protected void onPostExecute(Void arg) {
+                if (!isCancelled()) {
+                    progressView.setVisibility(View.INVISIBLE);
+                    if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) {
+                        mCropView.setTileSource(
+                                new BitmapRegionTileSource(context, bitmapSource), null);
+                        mCropView.setTouchEnabled(touchEnabled);
+                        if (moveToLeft) {
+                            mCropView.moveToLeft();
+                        }
+                    }
+                }
+                if (postExecute != null) {
+                    postExecute.run();
+                }
+            }
+        };
+        // We don't want to show the spinner every time we load an image, because that would be
+        // annoying; instead, only start showing the spinner if loading the image has taken
+        // longer than 1 sec (ie 1000 ms)
+        progressView.postDelayed(new Runnable() {
+            public void run() {
+                if (loadBitmapTask.getStatus() != AsyncTask.Status.FINISHED) {
+                    progressView.setVisibility(View.VISIBLE);
+                }
+            }
+        }, 1000);
+        loadBitmapTask.execute();
     }
 
     public boolean enableRotation() {
@@ -192,16 +247,18 @@
     private static int getRotationFromExifHelper(
             String path, Resources res, int resId, Context context, Uri uri) {
         ExifInterface ei = new ExifInterface();
+        InputStream is = null;
+        BufferedInputStream bis = null;
         try {
             if (path != null) {
                 ei.readExif(path);
             } else if (uri != null) {
-                InputStream is = context.getContentResolver().openInputStream(uri);
-                BufferedInputStream bis = new BufferedInputStream(is);
+                is = context.getContentResolver().openInputStream(uri);
+                bis = new BufferedInputStream(is);
                 ei.readExif(bis);
             } else {
-                InputStream is = res.openRawResource(resId);
-                BufferedInputStream bis = new BufferedInputStream(is);
+                is = res.openRawResource(resId);
+                bis = new BufferedInputStream(is);
                 ei.readExif(bis);
             }
             Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION);
@@ -210,6 +267,9 @@
             }
         } catch (IOException e) {
             Log.w(LOGTAG, "Getting exif data failed", e);
+        } finally {
+            Utils.closeSilently(bis);
+            Utils.closeSilently(is);
         }
         return 0;
     }
@@ -372,7 +432,6 @@
         String mInFilePath;
         byte[] mInImageBytes;
         int mInResId = 0;
-        InputStream mInStream;
         RectF mCropBounds = null;
         int mOutWidth, mOutHeight;
         int mRotation;
@@ -445,37 +504,36 @@
         }
 
         // Helper to setup input stream
-        private void regenerateInputStream() {
+        private InputStream regenerateInputStream() {
             if (mInUri == null && mInResId == 0 && mInFilePath == null && mInImageBytes == null) {
                 Log.w(LOGTAG, "cannot read original file, no input URI, resource ID, or " +
                         "image byte array given");
             } else {
-                Utils.closeSilently(mInStream);
                 try {
                     if (mInUri != null) {
-                        mInStream = new BufferedInputStream(
+                        return new BufferedInputStream(
                                 mContext.getContentResolver().openInputStream(mInUri));
                     } else if (mInFilePath != null) {
-                        mInStream = mContext.openFileInput(mInFilePath);
+                        return mContext.openFileInput(mInFilePath);
                     } else if (mInImageBytes != null) {
-                        mInStream = new BufferedInputStream(
-                                new ByteArrayInputStream(mInImageBytes));
+                        return new BufferedInputStream(new ByteArrayInputStream(mInImageBytes));
                     } else {
-                        mInStream = new BufferedInputStream(
-                                mResources.openRawResource(mInResId));
+                        return new BufferedInputStream(mResources.openRawResource(mInResId));
                     }
                 } catch (FileNotFoundException e) {
                     Log.w(LOGTAG, "cannot read file: " + mInUri.toString(), e);
                 }
             }
+            return null;
         }
 
         public Point getImageBounds() {
-            regenerateInputStream();
-            if (mInStream != null) {
+            InputStream is = regenerateInputStream();
+            if (is != null) {
                 BitmapFactory.Options options = new BitmapFactory.Options();
                 options.inJustDecodeBounds = true;
-                BitmapFactory.decodeStream(mInStream, null, options);
+                BitmapFactory.decodeStream(is, null, options);
+                Utils.closeSilently(is);
                 if (options.outWidth != 0 && options.outHeight != 0) {
                     return new Point(options.outWidth, options.outHeight);
                 }
@@ -493,22 +551,26 @@
         public boolean cropBitmap() {
             boolean failure = false;
 
-            regenerateInputStream();
 
             WallpaperManager wallpaperManager = null;
             if (mSetWallpaper) {
                 wallpaperManager = WallpaperManager.getInstance(mContext.getApplicationContext());
             }
-            if (mSetWallpaper && mNoCrop && mInStream != null) {
+
+
+            if (mSetWallpaper && mNoCrop) {
                 try {
-                    wallpaperManager.setStream(mInStream);
+                    InputStream is = regenerateInputStream();
+                    if (is != null) {
+                        wallpaperManager.setStream(is);
+                        Utils.closeSilently(is);
+                    }
                 } catch (IOException e) {
                     Log.w(LOGTAG, "cannot write stream to wallpaper", e);
                     failure = true;
                 }
                 return !failure;
-            }
-            if (mInStream != null) {
+            } else {
                 // Find crop bounds (scaled to original image size)
                 Rect roundedTrueCrop = new Rect();
                 Matrix rotateMatrix = new Matrix();
@@ -521,6 +583,11 @@
                     mCropBounds = new RectF(roundedTrueCrop);
 
                     Point bounds = getImageBounds();
+                    if (bounds == null) {
+                        Log.w(LOGTAG, "cannot get bounds for image");
+                        failure = true;
+                        return false;
+                    }
 
                     float[] rotatedBounds = new float[] { bounds.x, bounds.y };
                     rotateMatrix.mapPoints(rotatedBounds);
@@ -531,7 +598,6 @@
                     inverseRotateMatrix.mapRect(mCropBounds);
                     mCropBounds.offset(bounds.x/2, bounds.y/2);
 
-                    regenerateInputStream();
                 }
 
                 mCropBounds.roundOut(roundedTrueCrop);
@@ -543,15 +609,25 @@
                 }
 
                 // See how much we're reducing the size of the image
-                int scaleDownSampleSize = Math.min(roundedTrueCrop.width() / mOutWidth,
-                        roundedTrueCrop.height() / mOutHeight);
-
+                int scaleDownSampleSize = Math.max(1, Math.min(roundedTrueCrop.width() / mOutWidth,
+                        roundedTrueCrop.height() / mOutHeight));
                 // Attempt to open a region decoder
                 BitmapRegionDecoder decoder = null;
+                InputStream is = null;
                 try {
-                    decoder = BitmapRegionDecoder.newInstance(mInStream, true);
+                    is = regenerateInputStream();
+                    if (is == null) {
+                        Log.w(LOGTAG, "cannot get input stream for uri=" + mInUri.toString());
+                        failure = true;
+                        return false;
+                    }
+                    decoder = BitmapRegionDecoder.newInstance(is, false);
+                    Utils.closeSilently(is);
                 } catch (IOException e) {
                     Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e);
+                } finally {
+                   Utils.closeSilently(is);
+                   is = null;
                 }
 
                 Bitmap crop = null;
@@ -567,14 +643,15 @@
 
                 if (crop == null) {
                     // BitmapRegionDecoder has failed, try to crop in-memory
-                    regenerateInputStream();
+                    is = regenerateInputStream();
                     Bitmap fullSize = null;
-                    if (mInStream != null) {
+                    if (is != null) {
                         BitmapFactory.Options options = new BitmapFactory.Options();
                         if (scaleDownSampleSize > 1) {
                             options.inSampleSize = scaleDownSampleSize;
                         }
-                        fullSize = BitmapFactory.decodeStream(mInStream, null, options);
+                        fullSize = BitmapFactory.decodeStream(is, null, options);
+                        Utils.closeSilently(is);
                     }
                     if (fullSize != null) {
                         mCropBounds.left /= scaleDownSampleSize;
diff --git a/src/com/android/launcher3/WallpaperPickerActivity.java b/src/com/android/launcher3/WallpaperPickerActivity.java
index 2ad9218..efc3110 100644
--- a/src/com/android/launcher3/WallpaperPickerActivity.java
+++ b/src/com/android/launcher3/WallpaperPickerActivity.java
@@ -42,6 +42,7 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.LevelListDrawable;
 import android.net.Uri;
+import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
 import android.provider.MediaStore;
@@ -66,8 +67,10 @@
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.ListAdapter;
+import android.widget.Toast;
 
 import com.android.photos.BitmapRegionTileSource;
+import com.android.photos.BitmapRegionTileSource.BitmapSource;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -83,13 +86,12 @@
     private static final String TEMP_WALLPAPER_TILES = "TEMP_WALLPAPER_TILES";
     private static final String DEFAULT_WALLPAPER_THUMBNAIL_FILENAME = "default_thumb.jpg";
 
-    private View mSelectedThumb;
+    private View mSelectedTile;
     private boolean mIgnoreNextTap;
     private OnClickListener mThumbnailOnClickListener;
 
     private LinearLayout mWallpapersView;
     private View mWallpaperStrip;
-    private ImageView mDefaultWallpaperView;
 
     private ActionMode.Callback mActionModeCallback;
     private ActionMode mActionMode;
@@ -128,16 +130,39 @@
 
     public static class UriWallpaperInfo extends WallpaperTileInfo {
         private Uri mUri;
+        private boolean mFirstClick = true;
+        private BitmapRegionTileSource.UriBitmapSource mBitmapSource;
         public UriWallpaperInfo(Uri uri) {
             mUri = uri;
         }
         @Override
-        public void onClick(WallpaperPickerActivity a) {
-            CropView v = a.getCropView();
-            int rotation = WallpaperCropActivity.getRotationFromExif(a, mUri);
-            a.getDefaultWallpaperView().setVisibility(View.INVISIBLE);
-            v.setTileSource(new BitmapRegionTileSource(a, mUri, 1024, rotation), null);
-            v.setTouchEnabled(true);
+        public void onClick(final WallpaperPickerActivity a) {
+            final Runnable onLoad;
+            if (!mFirstClick) {
+                onLoad = null;
+            } else {
+                mFirstClick = false;
+                onLoad = new Runnable() {
+                    public void run() {
+                        if (mBitmapSource != null &&
+                                mBitmapSource.getLoadingState() == BitmapSource.State.LOADED) {
+                            mView.setVisibility(View.VISIBLE);
+                            a.selectTile(mView);
+                        } else {
+                            ViewGroup parent = (ViewGroup) mView.getParent();
+                            if (parent != null) {
+                                parent.removeView(mView);
+                                Toast.makeText(a,
+                                        a.getString(R.string.image_load_fail),
+                                        Toast.LENGTH_SHORT).show();
+                            }
+                        }
+                    }
+                };
+            }
+            mBitmapSource = new BitmapRegionTileSource.UriBitmapSource(
+                    a, mUri, BitmapRegionTileSource.MAX_PREVIEW_SIZE);
+            a.setCropViewTileSource(mBitmapSource, true, false, onLoad);
         }
         @Override
         public void onSave(final WallpaperPickerActivity a) {
@@ -175,11 +200,12 @@
         }
         @Override
         public void onClick(WallpaperPickerActivity a) {
-            int rotation = WallpaperCropActivity.getRotationFromExif(mResources, mResId);
-            BitmapRegionTileSource source = new BitmapRegionTileSource(
-                    mResources, a, mResId, 1024, rotation);
+            BitmapRegionTileSource.ResourceBitmapSource bitmapSource =
+                    new BitmapRegionTileSource.ResourceBitmapSource(
+                            mResources, mResId, BitmapRegionTileSource.MAX_PREVIEW_SIZE);
+            bitmapSource.loadInBackground();
+            BitmapRegionTileSource source = new BitmapRegionTileSource(a, bitmapSource);
             CropView v = a.getCropView();
-            a.getDefaultWallpaperView().setVisibility(View.INVISIBLE);
             v.setTileSource(source, null);
             Point wallpaperSize = WallpaperCropActivity.getDefaultWallpaperSize(
                     a.getResources(), a.getWindowManager());
@@ -211,15 +237,15 @@
         }
         @Override
         public void onClick(WallpaperPickerActivity a) {
-            a.getCropView().setTouchEnabled(false);
-            ImageView defaultWallpaperView = a.getDefaultWallpaperView();
-            defaultWallpaperView.setVisibility(View.VISIBLE);
+            CropView c = a.getCropView();
+
             Drawable defaultWallpaper = WallpaperManager.getInstance(a).getBuiltInDrawable(
-                    defaultWallpaperView.getWidth(), defaultWallpaperView.getHeight(),
-                    false, 0.5f, 0.5f);
-            if (defaultWallpaper != null) {
-                defaultWallpaperView.setBackgroundDrawable(defaultWallpaper);
-            }
+                    c.getWidth(), c.getHeight(), false, 0.5f, 0.5f);
+
+            c.setTileSource(
+                    new DrawableTileSource(a, defaultWallpaper, DrawableTileSource.MAX_PREVIEW_SIZE), null);
+            c.setScale(1f);
+            c.setTouchEnabled(false);
         }
         @Override
         public void onSave(WallpaperPickerActivity a) {
@@ -249,7 +275,6 @@
         setContentView(R.layout.wallpaper_picker);
 
         mCropView = (CropView) findViewById(R.id.cropView);
-        mDefaultWallpaperView = (ImageView) findViewById(R.id.defaultWallpaperView);
         mWallpaperStrip = findViewById(R.id.wallpaper_strip);
         mCropView.setTouchCallback(new CropView.TouchCallback() {
             LauncherViewPropertyAnimator mAnim;
@@ -307,17 +332,8 @@
                     return;
                 }
                 WallpaperTileInfo info = (WallpaperTileInfo) v.getTag();
-                if (info.isSelectable()) {
-                    if (mSelectedThumb != null) {
-                        mSelectedThumb.setSelected(false);
-                        mSelectedThumb = null;
-                    }
-                    mSelectedThumb = v;
-                    v.setSelected(true);
-                    // TODO: Remove this once the accessibility framework and
-                    // services have better support for selection state.
-                    v.announceForAccessibility(
-                            getString(R.string.announce_selection, v.getContentDescription()));
+                if (info.isSelectable() && v.getVisibility() == View.VISIBLE) {
+                    selectTile(v);
                 }
                 info.onClick(WallpaperPickerActivity.this);
             }
@@ -410,7 +426,7 @@
 
         // Select the first item; wait for a layout pass so that we initialize the dimensions of
         // cropView or the defaultWallpaperView first
-        mDefaultWallpaperView.addOnLayoutChangeListener(new OnLayoutChangeListener() {
+        mCropView.addOnLayoutChangeListener(new OnLayoutChangeListener() {
             @Override
             public void onLayoutChange(View v, int left, int top, int right, int bottom,
                     int oldLeft, int oldTop, int oldRight, int oldBottom) {
@@ -443,8 +459,8 @@
                 new View.OnClickListener() {
                     @Override
                     public void onClick(View v) {
-                        if (mSelectedThumb != null) {
-                            WallpaperTileInfo info = (WallpaperTileInfo) mSelectedThumb.getTag();
+                        if (mSelectedTile != null) {
+                            WallpaperTileInfo info = (WallpaperTileInfo) mSelectedTile.getTag();
                             info.onSave(WallpaperPickerActivity.this);
                         }
                     }
@@ -523,12 +539,25 @@
                     CheckableFrameLayout c = (CheckableFrameLayout) mWallpapersView.getChildAt(i);
                     c.setChecked(false);
                 }
-                mSelectedThumb.setSelected(true);
+                mSelectedTile.setSelected(true);
                 mActionMode = null;
             }
         };
     }
 
+    private void selectTile(View v) {
+        if (mSelectedTile != null) {
+            mSelectedTile.setSelected(false);
+            mSelectedTile = null;
+        }
+        mSelectedTile = v;
+        v.setSelected(true);
+        // TODO: Remove this once the accessibility framework and
+        // services have better support for selection state.
+        v.announceForAccessibility(
+                getString(R.string.announce_selection, v.getContentDescription()));
+    }
+
     private void initializeScrollForRtl() {
         final HorizontalScrollView scroll =
                 (HorizontalScrollView) findViewById(R.id.wallpaper_scroll_container);
@@ -687,26 +716,35 @@
         }
     }
 
-    private void addTemporaryWallpaperTile(Uri uri) {
+    private void addTemporaryWallpaperTile(final Uri uri) {
         mTempWallpaperTiles.add(uri);
         // Add a tile for the image picked from Gallery
-        FrameLayout pickedImageThumbnail = (FrameLayout) getLayoutInflater().
+        final FrameLayout pickedImageThumbnail = (FrameLayout) getLayoutInflater().
                 inflate(R.layout.wallpaper_picker_item, mWallpapersView, false);
+        pickedImageThumbnail.setVisibility(View.GONE);
         setWallpaperItemPaddingToZero(pickedImageThumbnail);
+        mWallpapersView.addView(pickedImageThumbnail, 0);
 
         // Load the thumbnail
-        ImageView image = (ImageView) pickedImageThumbnail.findViewById(R.id.wallpaper_image);
-        Point defaultSize = getDefaultThumbnailSize(this.getResources());
-        int rotation = WallpaperCropActivity.getRotationFromExif(this, uri);
-        Bitmap thumb = createThumbnail(defaultSize, this, uri, null, null, 0, rotation, false);
-        if (thumb != null) {
-            image.setImageBitmap(thumb);
-            Drawable thumbDrawable = image.getDrawable();
-            thumbDrawable.setDither(true);
-        } else {
-            Log.e(TAG, "Error loading thumbnail for uri=" + uri);
-        }
-        mWallpapersView.addView(pickedImageThumbnail, 0);
+        final ImageView image = (ImageView) pickedImageThumbnail.findViewById(R.id.wallpaper_image);
+        final Point defaultSize = getDefaultThumbnailSize(this.getResources());
+        final Context context = this;
+        new AsyncTask<Void, Bitmap, Bitmap>() {
+            protected Bitmap doInBackground(Void...args) {
+                int rotation = WallpaperCropActivity.getRotationFromExif(context, uri);
+                return createThumbnail(defaultSize, context, uri, null, null, 0, rotation, false);
+
+            }
+            protected void onPostExecute(Bitmap thumb) {
+                if (thumb != null) {
+                    image.setImageBitmap(thumb);
+                    Drawable thumbDrawable = image.getDrawable();
+                    thumbDrawable.setDither(true);
+                } else {
+                    Log.e(TAG, "Error loading thumbnail for uri=" + uri);
+                }
+            }
+        }.execute();
 
         UriWallpaperInfo info = new UriWallpaperInfo(uri);
         pickedImageThumbnail.setTag(info);
@@ -886,10 +924,6 @@
         return mCropView;
     }
 
-    public ImageView getDefaultWallpaperView() {
-        return mDefaultWallpaperView;
-    }
-
     public SavedWallpaperImages getSavedImages() {
         return mSavedImages;
     }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index a1353c3..d742d42 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -22,6 +22,7 @@
 import android.animation.AnimatorSet;
 import android.animation.LayoutTransition;
 import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
@@ -89,6 +90,9 @@
     private static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375;
     private static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 100;
 
+    protected static final int SNAP_OFF_EMPTY_SCREEN_DURATION = 400;
+    protected static final int FADE_EMPTY_SCREEN_DURATION = 150;
+
     private static final int BACKGROUND_FADE_OUT_DURATION = 350;
     private static final int ADJACENT_SCREEN_DROP_DURATION = 300;
     private static final int FLING_THRESHOLD_VELOCITY = 500;
@@ -128,6 +132,8 @@
     private HashMap<Long, CellLayout> mWorkspaceScreens = new HashMap<Long, CellLayout>();
     private ArrayList<Long> mScreenOrder = new ArrayList<Long>();
 
+    private Runnable mRemoveEmptyScreenRunnable;
+
     /**
      * CellInfo for the cell that is currently being dragged
      */
@@ -392,7 +398,6 @@
         InstallShortcutReceiver.disableAndFlushInstallQueue(getContext());
         UninstallShortcutReceiver.disableAndFlushUninstallQueue(getContext());
 
-        removeExtraEmptyScreen();
         mDragSourceInternal = null;
         mLauncher.onInteractionEnd();
     }
@@ -627,6 +632,9 @@
         boolean lastChildOnScreen = false;
         boolean childOnFinalScreen = false;
 
+        // Cancel any pending removal of empty screen
+        mRemoveEmptyScreenRunnable = null;
+
         if (mDragSourceInternal != null) {
             if (mDragSourceInternal.getChildCount() == 1) {
                 lastChildOnScreen = true;
@@ -654,15 +662,97 @@
         return false;
     }
 
-    public void removeExtraEmptyScreen() {
-        if (hasExtraEmptyScreen()) {
-            CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
-            mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
-            mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID);
-            removeView(cl);
+    private void convertFinalScreenToEmptyScreenIfNecessary() {
+        if (hasExtraEmptyScreen() || mScreenOrder.size() == 0) return;
+        long finalScreenId = mScreenOrder.get(mScreenOrder.size() - 1);
+
+        if (finalScreenId == CUSTOM_CONTENT_SCREEN_ID) return;
+        CellLayout finalScreen = mWorkspaceScreens.get(finalScreenId);
+
+        // If the final screen is empty, convert it to the extra empty screen
+        if (finalScreen.getShortcutsAndWidgets().getChildCount() == 0) {
+            mWorkspaceScreens.remove(finalScreenId);
+            mScreenOrder.remove(finalScreenId);
+
+            // if this is the last non-custom content screen, convert it to the empty screen
+            mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, finalScreen);
+            mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
         }
     }
 
+    public void removeExtraEmptyScreen(final boolean animate, final Runnable onComplete) {
+        removeExtraEmptyScreen(animate, onComplete, 0, false);
+    }
+
+    public void removeExtraEmptyScreen(final boolean animate, final Runnable onComplete,
+            final int delay, final boolean stripEmptyScreens) {
+        if (delay > 0) {
+            postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    removeExtraEmptyScreen(animate, onComplete, 0, stripEmptyScreens);
+                }
+
+            }, delay);
+            return;
+        }
+
+        convertFinalScreenToEmptyScreenIfNecessary();
+        if (hasExtraEmptyScreen()) {
+            int emptyIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
+            if (getNextPage() == emptyIndex) {
+                snapToPage(getNextPage() - 1, SNAP_OFF_EMPTY_SCREEN_DURATION);
+                fadeAndRemoveEmptyScreen(SNAP_OFF_EMPTY_SCREEN_DURATION, FADE_EMPTY_SCREEN_DURATION,
+                        onComplete, stripEmptyScreens);
+            } else {
+                fadeAndRemoveEmptyScreen(0, FADE_EMPTY_SCREEN_DURATION,
+                        onComplete, stripEmptyScreens);
+            }
+            return;
+        }
+        if (onComplete != null) {
+            onComplete.run();
+        }
+    }
+
+    private void fadeAndRemoveEmptyScreen(int delay, int duration, final Runnable onComplete,
+            final boolean stripEmptyScreens) {
+        PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0f);
+        PropertyValuesHolder bgAlpha = PropertyValuesHolder.ofFloat("backgroundAlpha", 0f);
+
+        final CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
+
+        mRemoveEmptyScreenRunnable = new Runnable() {
+            @Override
+            public void run() {
+                if (hasExtraEmptyScreen()) {
+                    mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
+                    mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID);
+                    removeView(cl);
+                    if (stripEmptyScreens) {
+                        stripEmptyScreens();
+                    }
+                }
+            }
+        };
+
+        ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(cl, alpha, bgAlpha);
+        oa.setDuration(duration);
+        oa.setStartDelay(delay);
+        oa.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (mRemoveEmptyScreenRunnable != null) {
+                    mRemoveEmptyScreenRunnable.run();
+                }
+                if (onComplete != null) {
+                    onComplete.run();
+                }
+            }
+        });
+        oa.start();
+    }
+
     public boolean hasExtraEmptyScreen() {
         int nScreens = getChildCount();
         nScreens = nScreens - numCustomPages();
@@ -753,6 +843,7 @@
                 removeView(cl);
             } else {
                 // if this is the last non-custom content screen, convert it to the empty screen
+                mRemoveEmptyScreenRunnable = null;
                 mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, cl);
                 mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
             }
@@ -1103,11 +1194,15 @@
     }
 
     protected void snapToPage(int whichPage, Runnable r) {
+        snapToPage(whichPage, SLOW_PAGE_SNAP_ANIMATION_DURATION, r);
+    }
+
+    protected void snapToPage(int whichPage, int duration, Runnable r) {
         if (mDelayedSnapToPageRunnable != null) {
             mDelayedSnapToPageRunnable.run();
         }
         mDelayedSnapToPageRunnable = r;
-        snapToPage(whichPage, SLOW_PAGE_SNAP_ANIMATION_DURATION);
+        snapToPage(whichPage, duration);
     }
 
     protected void snapToScreenId(long screenId, Runnable r) {
@@ -2726,13 +2821,13 @@
                 // cell also contains a shortcut, then create a folder with the two shortcuts.
                 if (!mInScrollArea && createUserFolderIfNecessary(cell, container,
                         dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) {
-                    stripEmptyScreens();
+                    removeExtraEmptyScreen(true, null, 0, true);
                     return;
                 }
 
                 if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
                         distance, d, false)) {
-                    stripEmptyScreens();
+                    removeExtraEmptyScreen(true, null, 0, true);
                     return;
                 }
 
@@ -2840,7 +2935,7 @@
                     if (finalResizeRunnable != null) {
                         finalResizeRunnable.run();
                     }
-                    stripEmptyScreens();
+                    removeExtraEmptyScreen(true, null, 0, true);
                 }
             };
             mAnimatingViewIntoPlace = true;
@@ -3491,7 +3586,13 @@
         final Runnable exitSpringLoadedRunnable = new Runnable() {
             @Override
             public void run() {
-                mLauncher.exitSpringLoadedDragModeDelayed(true, false, null);
+                removeExtraEmptyScreen(false, new Runnable() {
+                    @Override
+                    public void run() {
+                        mLauncher.exitSpringLoadedDragModeDelayed(true,
+                                Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
+                    }
+                });
             }
         };
 
@@ -3727,7 +3828,7 @@
                 external, scalePreview);
 
         Resources res = mLauncher.getResources();
-        int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200;
+        final int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200;
 
         // In the case where we've prebound the widget, we remove it from the DragLayer
         if (finalView instanceof AppWidgetHostView && external) {
@@ -3835,11 +3936,11 @@
             final boolean isFlingToDelete, final boolean success) {
         if (mDeferDropAfterUninstall) {
             mDeferredAction = new Runnable() {
-                    public void run() {
-                        onDropCompleted(target, d, isFlingToDelete, success);
-                        mDeferredAction = null;
-                    }
-                };
+                public void run() {
+                    onDropCompleted(target, d, isFlingToDelete, success);
+                    mDeferredAction = null;
+                }
+            };
             return;
         }
 
@@ -3857,7 +3958,7 @@
                 // If we move the item to anything not on the Workspace, check if any empty
                 // screens need to be removed. If we dropped back on the workspace, this will
                 // be done post drop animation.
-                stripEmptyScreens();
+                removeExtraEmptyScreen(true, null, 0, true);
             }
         } else if (mDragInfo != null) {
             CellLayout cellLayout;
@@ -4085,6 +4186,7 @@
             }
         }
         mRestoredPages.clear();
+        mSavedStates = null;
     }
 
     @Override
diff --git a/src/com/android/photos/BitmapRegionTileSource.java b/src/com/android/photos/BitmapRegionTileSource.java
index 5f64018..2f203af 100644
--- a/src/com/android/photos/BitmapRegionTileSource.java
+++ b/src/com/android/photos/BitmapRegionTileSource.java
@@ -24,6 +24,9 @@
 import android.graphics.BitmapFactory;
 import android.graphics.BitmapRegionDecoder;
 import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Build;
@@ -31,14 +34,114 @@
 import android.util.Log;
 
 import com.android.gallery3d.common.BitmapUtils;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.exif.ExifInterface;
 import com.android.gallery3d.glrenderer.BasicTexture;
 import com.android.gallery3d.glrenderer.BitmapTexture;
 import com.android.photos.views.TiledImageRenderer;
 
 import java.io.BufferedInputStream;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 
+interface SimpleBitmapRegionDecoder {
+    int getWidth();
+    int getHeight();
+    Bitmap decodeRegion(Rect wantRegion, BitmapFactory.Options options);
+}
+
+class SimpleBitmapRegionDecoderWrapper implements SimpleBitmapRegionDecoder {
+    BitmapRegionDecoder mDecoder;
+    private SimpleBitmapRegionDecoderWrapper(BitmapRegionDecoder decoder) {
+        mDecoder = decoder;
+    }
+    public static SimpleBitmapRegionDecoderWrapper newInstance(
+            String pathName, boolean isShareable) {
+        try {
+            BitmapRegionDecoder d = BitmapRegionDecoder.newInstance(pathName, isShareable);
+            if (d != null) {
+                return new SimpleBitmapRegionDecoderWrapper(d);
+            }
+        } catch (IOException e) {
+            Log.w("BitmapRegionTileSource", "getting decoder failed for path " + pathName, e);
+            return null;
+        }
+        return null;
+    }
+    public static SimpleBitmapRegionDecoderWrapper newInstance(
+            InputStream is, boolean isShareable) {
+        try {
+            BitmapRegionDecoder d = BitmapRegionDecoder.newInstance(is, isShareable);
+            if (d != null) {
+                return new SimpleBitmapRegionDecoderWrapper(d);
+            }
+        } catch (IOException e) {
+            Log.w("BitmapRegionTileSource", "getting decoder failed", e);
+            return null;
+        }
+        return null;
+    }
+    public int getWidth() {
+        return mDecoder.getWidth();
+    }
+    public int getHeight() {
+        return mDecoder.getHeight();
+    }
+    public Bitmap decodeRegion(Rect wantRegion, BitmapFactory.Options options) {
+        return mDecoder.decodeRegion(wantRegion, options);
+    }
+}
+
+class DumbBitmapRegionDecoder implements SimpleBitmapRegionDecoder {
+    //byte[] mStreamCopy;
+    Bitmap mBuffer;
+    Canvas mTempCanvas;
+    Paint mTempPaint;
+    private DumbBitmapRegionDecoder(Bitmap b) {
+        mBuffer = b;
+    }
+    public static DumbBitmapRegionDecoder newInstance(String pathName) {
+        Bitmap b = BitmapFactory.decodeFile(pathName);
+        if (b != null) {
+            return new DumbBitmapRegionDecoder(b);
+        }
+        return null;
+    }
+    public static DumbBitmapRegionDecoder newInstance(InputStream is) {
+        Bitmap b = BitmapFactory.decodeStream(is);
+        if (b != null) {
+            return new DumbBitmapRegionDecoder(b);
+        }
+        return null;
+    }
+    public int getWidth() {
+        return mBuffer.getWidth();
+    }
+    public int getHeight() {
+        return mBuffer.getHeight();
+    }
+    public Bitmap decodeRegion(Rect wantRegion, BitmapFactory.Options options) {
+        if (mTempCanvas == null) {
+            mTempCanvas = new Canvas();
+            mTempPaint = new Paint();
+            mTempPaint.setFilterBitmap(true);
+        }
+        int sampleSize = Math.max(options.inSampleSize, 1);
+        Bitmap newBitmap = Bitmap.createBitmap(
+                wantRegion.width() / sampleSize,
+                wantRegion.height() / sampleSize,
+                Bitmap.Config.ARGB_8888);
+        mTempCanvas.setBitmap(newBitmap);
+        mTempCanvas.save();
+        mTempCanvas.scale(1f / sampleSize, 1f / sampleSize);
+        mTempCanvas.drawBitmap(mBuffer, -wantRegion.left, -wantRegion.top, mTempPaint);
+        mTempCanvas.restore();
+        mTempCanvas.setBitmap(null);
+        return newBitmap;
+    }
+}
+
 /**
  * A {@link com.android.photos.views.TiledImageRenderer.TileSource} using
  * {@link BitmapRegionDecoder} to wrap a local file
@@ -53,9 +156,214 @@
     private static final int GL_SIZE_LIMIT = 2048;
     // This must be no larger than half the size of the GL_SIZE_LIMIT
     // due to decodePreview being allowed to be up to 2x the size of the target
-    private static final int MAX_PREVIEW_SIZE = 1024;
+    public static final int MAX_PREVIEW_SIZE = GL_SIZE_LIMIT / 2;
 
-    BitmapRegionDecoder mDecoder;
+    public static abstract class BitmapSource {
+        private SimpleBitmapRegionDecoder mDecoder;
+        private Bitmap mPreview;
+        private int mPreviewSize;
+        private int mRotation;
+        public enum State { NOT_LOADED, LOADED, ERROR_LOADING };
+        private State mState = State.NOT_LOADED;
+        public BitmapSource(int previewSize) {
+            mPreviewSize = previewSize;
+        }
+        public boolean loadInBackground() {
+            ExifInterface ei = new ExifInterface();
+            if (readExif(ei)) {
+                Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION);
+                if (ori != null) {
+                    mRotation = ExifInterface.getRotationForOrientationValue(ori.shortValue());
+                }
+            }
+            mDecoder = loadBitmapRegionDecoder();
+            if (mDecoder == null) {
+                mState = State.ERROR_LOADING;
+                return false;
+            } else {
+                int width = mDecoder.getWidth();
+                int height = mDecoder.getHeight();
+                if (mPreviewSize != 0) {
+                    int previewSize = Math.min(mPreviewSize, MAX_PREVIEW_SIZE);
+                    BitmapFactory.Options opts = new BitmapFactory.Options();
+                    opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
+                    opts.inPreferQualityOverSpeed = true;
+
+                    float scale = (float) previewSize / Math.max(width, height);
+                    opts.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale);
+                    opts.inJustDecodeBounds = false;
+                    mPreview = loadPreviewBitmap(opts);
+                }
+                mState = State.LOADED;
+                return true;
+            }
+        }
+
+        public State getLoadingState() {
+            return mState;
+        }
+
+        public SimpleBitmapRegionDecoder getBitmapRegionDecoder() {
+            return mDecoder;
+        }
+
+        public Bitmap getPreviewBitmap() {
+            return mPreview;
+        }
+
+        public int getPreviewSize() {
+            return mPreviewSize;
+        }
+
+        public int getRotation() {
+            return mRotation;
+        }
+
+        public abstract boolean readExif(ExifInterface ei);
+        public abstract SimpleBitmapRegionDecoder loadBitmapRegionDecoder();
+        public abstract Bitmap loadPreviewBitmap(BitmapFactory.Options options);
+    }
+
+    public static class FilePathBitmapSource extends BitmapSource {
+        private String mPath;
+        public FilePathBitmapSource(String path, int previewSize) {
+            super(previewSize);
+            mPath = path;
+        }
+        @Override
+        public SimpleBitmapRegionDecoder loadBitmapRegionDecoder() {
+            SimpleBitmapRegionDecoder d;
+            d = SimpleBitmapRegionDecoderWrapper.newInstance(mPath, true);
+            if (d == null) {
+                d = DumbBitmapRegionDecoder.newInstance(mPath);
+            }
+            return d;
+        }
+        @Override
+        public Bitmap loadPreviewBitmap(BitmapFactory.Options options) {
+            return BitmapFactory.decodeFile(mPath, options);
+        }
+        @Override
+        public boolean readExif(ExifInterface ei) {
+            try {
+                ei.readExif(mPath);
+                return true;
+            } catch (IOException e) {
+                Log.w("BitmapRegionTileSource", "getting decoder failed", e);
+                return false;
+            }
+        }
+    }
+
+    public static class UriBitmapSource extends BitmapSource {
+        private Context mContext;
+        private Uri mUri;
+        public UriBitmapSource(Context context, Uri uri, int previewSize) {
+            super(previewSize);
+            mContext = context;
+            mUri = uri;
+        }
+        private InputStream regenerateInputStream() throws FileNotFoundException {
+            InputStream is = mContext.getContentResolver().openInputStream(mUri);
+            return new BufferedInputStream(is);
+        }
+        @Override
+        public SimpleBitmapRegionDecoder loadBitmapRegionDecoder() {
+            try {
+                InputStream is = regenerateInputStream();
+                SimpleBitmapRegionDecoder regionDecoder =
+                        SimpleBitmapRegionDecoderWrapper.newInstance(is, false);
+                Utils.closeSilently(is);
+                if (regionDecoder == null) {
+                    is = regenerateInputStream();
+                    regionDecoder = DumbBitmapRegionDecoder.newInstance(is);
+                    Utils.closeSilently(is);
+                }
+                return regionDecoder;
+            } catch (FileNotFoundException e) {
+                Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
+                return null;
+            } catch (IOException e) {
+                Log.e("BitmapRegionTileSource", "Failure while reading URI " + mUri, e);
+                return null;
+            }
+        }
+        @Override
+        public Bitmap loadPreviewBitmap(BitmapFactory.Options options) {
+            try {
+                InputStream is = regenerateInputStream();
+                Bitmap b = BitmapFactory.decodeStream(is, null, options);
+                Utils.closeSilently(is);
+                return b;
+            } catch (FileNotFoundException e) {
+                Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
+                return null;
+            }
+        }
+        @Override
+        public boolean readExif(ExifInterface ei) {
+            InputStream is = null;
+            try {
+                is = regenerateInputStream();
+                ei.readExif(is);
+                Utils.closeSilently(is);
+                return true;
+            } catch (FileNotFoundException e) {
+                Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
+                return false;
+            } catch (IOException e) {
+                Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
+                return false;
+            } finally {
+                Utils.closeSilently(is);
+            }
+        }
+    }
+
+    public static class ResourceBitmapSource extends BitmapSource {
+        private Resources mRes;
+        private int mResId;
+        public ResourceBitmapSource(Resources res, int resId, int previewSize) {
+            super(previewSize);
+            mRes = res;
+            mResId = resId;
+        }
+        private InputStream regenerateInputStream() {
+            InputStream is = mRes.openRawResource(mResId);
+            return new BufferedInputStream(is);
+        }
+        @Override
+        public SimpleBitmapRegionDecoder loadBitmapRegionDecoder() {
+            InputStream is = regenerateInputStream();
+            SimpleBitmapRegionDecoder regionDecoder =
+                    SimpleBitmapRegionDecoderWrapper.newInstance(is, false);
+            Utils.closeSilently(is);
+            if (regionDecoder == null) {
+                is = regenerateInputStream();
+                regionDecoder = DumbBitmapRegionDecoder.newInstance(is);
+                Utils.closeSilently(is);
+            }
+            return regionDecoder;
+        }
+        @Override
+        public Bitmap loadPreviewBitmap(BitmapFactory.Options options) {
+            return BitmapFactory.decodeResource(mRes, mResId, options);
+        }
+        @Override
+        public boolean readExif(ExifInterface ei) {
+            try {
+                InputStream is = regenerateInputStream();
+                ei.readExif(is);
+                Utils.closeSilently(is);
+                return true;
+            } catch (IOException e) {
+                Log.e("BitmapRegionTileSource", "Error reading resource", e);
+                return false;
+            }
+        }
+    }
+
+    SimpleBitmapRegionDecoder mDecoder;
     int mWidth;
     int mHeight;
     int mTileSize;
@@ -68,58 +376,33 @@
     private BitmapFactory.Options mOptions;
     private Canvas mCanvas;
 
-    public BitmapRegionTileSource(Context context, String path, int previewSize, int rotation) {
-        this(null, context, path, null, 0, previewSize, rotation);
-    }
-
-    public BitmapRegionTileSource(Context context, Uri uri, int previewSize, int rotation) {
-        this(null, context, null, uri, 0, previewSize, rotation);
-    }
-
-    public BitmapRegionTileSource(Resources res,
-            Context context, int resId, int previewSize, int rotation) {
-        this(res, context, null, null, resId, previewSize, rotation);
-    }
-
-    private BitmapRegionTileSource(Resources res,
-            Context context, String path, Uri uri, int resId, int previewSize, int rotation) {
+    public BitmapRegionTileSource(Context context, BitmapSource source) {
         mTileSize = TiledImageRenderer.suggestedTileSize(context);
-        mRotation = rotation;
-        try {
-            if (path != null) {
-                mDecoder = BitmapRegionDecoder.newInstance(path, true);
-            } else if (uri != null) {
-                InputStream is = context.getContentResolver().openInputStream(uri);
-                BufferedInputStream bis = new BufferedInputStream(is);
-                mDecoder = BitmapRegionDecoder.newInstance(bis, true);
-            } else {
-                InputStream is = res.openRawResource(resId);
-                BufferedInputStream bis = new BufferedInputStream(is);
-                mDecoder = BitmapRegionDecoder.newInstance(bis, true);
-            }
+        mRotation = source.getRotation();
+        mDecoder = source.getBitmapRegionDecoder();
+        if (mDecoder != null) {
             mWidth = mDecoder.getWidth();
             mHeight = mDecoder.getHeight();
-        } catch (IOException e) {
-            Log.w("BitmapRegionTileSource", "ctor failed", e);
-        }
-        mOptions = new BitmapFactory.Options();
-        mOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
-        mOptions.inPreferQualityOverSpeed = true;
-        mOptions.inTempStorage = new byte[16 * 1024];
-        if (previewSize != 0) {
-            previewSize = Math.min(previewSize, MAX_PREVIEW_SIZE);
-            // Although this is the same size as the Bitmap that is likely already
-            // loaded, the lifecycle is different and interactions are on a different
-            // thread. Thus to simplify, this source will decode its own bitmap.
-            Bitmap preview = decodePreview(res, context, path, uri, resId, previewSize);
-            if (preview.getWidth() <= GL_SIZE_LIMIT && preview.getHeight() <= GL_SIZE_LIMIT) {
-                mPreview = new BitmapTexture(preview);
-            } else {
-                Log.w(TAG, String.format(
-                        "Failed to create preview of apropriate size! "
-                        + " in: %dx%d, out: %dx%d",
-                        mWidth, mHeight,
-                        preview.getWidth(), preview.getHeight()));
+            mOptions = new BitmapFactory.Options();
+            mOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
+            mOptions.inPreferQualityOverSpeed = true;
+            mOptions.inTempStorage = new byte[16 * 1024];
+            int previewSize = source.getPreviewSize();
+            if (previewSize != 0) {
+                previewSize = Math.min(previewSize, MAX_PREVIEW_SIZE);
+                // Although this is the same size as the Bitmap that is likely already
+                // loaded, the lifecycle is different and interactions are on a different
+                // thread. Thus to simplify, this source will decode its own bitmap.
+                Bitmap preview = decodePreview(source, previewSize);
+                if (preview.getWidth() <= GL_SIZE_LIMIT && preview.getHeight() <= GL_SIZE_LIMIT) {
+                    mPreview = new BitmapTexture(preview);
+                } else {
+                    Log.w(TAG, String.format(
+                            "Failed to create preview of apropriate size! "
+                            + " in: %dx%d, out: %dx%d",
+                            mWidth, mHeight,
+                            preview.getWidth(), preview.getHeight()));
+                }
             }
         }
     }
@@ -215,33 +498,15 @@
      * Note that the returned bitmap may have a long edge that's longer
      * than the targetSize, but it will always be less than 2x the targetSize
      */
-    private Bitmap decodePreview(
-            Resources res, Context context, String file, Uri uri, int resId, int targetSize) {
-        float scale = (float) targetSize / Math.max(mWidth, mHeight);
-        mOptions.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale);
-        mOptions.inJustDecodeBounds = false;
-
-        Bitmap result = null;
-        if (file != null) {
-            result = BitmapFactory.decodeFile(file, mOptions);
-        } else if (uri != null) {
-            try {
-                InputStream is = context.getContentResolver().openInputStream(uri);
-                BufferedInputStream bis = new BufferedInputStream(is);
-                result = BitmapFactory.decodeStream(bis, null, mOptions);
-            } catch (IOException e) {
-                Log.w("BitmapRegionTileSource", "getting preview failed", e);
-            }
-        } else {
-            result = BitmapFactory.decodeResource(res, resId, mOptions);
-        }
+    private Bitmap decodePreview(BitmapSource source, int targetSize) {
+        Bitmap result = source.getPreviewBitmap();
         if (result == null) {
             return null;
         }
 
         // We need to resize down if the decoder does not support inSampleSize
         // or didn't support the specified inSampleSize (some decoders only do powers of 2)
-        scale = (float) targetSize / (float) (Math.max(result.getWidth(), result.getHeight()));
+        float scale = (float) targetSize / (float) (Math.max(result.getWidth(), result.getHeight()));
 
         if (scale <= 0.5) {
             result = BitmapUtils.resizeBitmapByScale(result, scale, true);
diff --git a/update_system_wallpaper_cropper.py b/update_system_wallpaper_cropper.py
new file mode 100644
index 0000000..5d24f6f
--- /dev/null
+++ b/update_system_wallpaper_cropper.py
@@ -0,0 +1,55 @@
+# This script is used to push the most up-to-date files from
+# Launcher into frameworks' version of the WallpaperCropActivity
+# (and supporting files)
+# The framework versions have some small modifications that are
+# necessary so do this with care
+import os
+import sys
+files = """
+src/android/util/Pools.java
+src/com/android/gallery3d/util/IntArray.java
+src/com/android/gallery3d/common/Utils.java
+src/com/android/gallery3d/exif/ByteBufferInputStream.java
+src/com/android/gallery3d/exif/CountedDataInputStream.java
+src/com/android/gallery3d/exif/ExifData.java
+src/com/android/gallery3d/exif/ExifInterface.java
+src/com/android/gallery3d/exif/ExifInvalidFormatException.java
+src/com/android/gallery3d/exif/ExifModifier.java
+src/com/android/gallery3d/exif/ExifOutputStream.java
+src/com/android/gallery3d/exif/ExifParser.java
+src/com/android/gallery3d/exif/ExifReader.java
+src/com/android/gallery3d/exif/ExifTag.java
+src/com/android/gallery3d/exif/IfdData.java
+src/com/android/gallery3d/exif/IfdId.java
+src/com/android/gallery3d/exif/JpegHeader.java
+src/com/android/gallery3d/exif/OrderedDataOutputStream.java
+src/com/android/gallery3d/exif/Rational.java
+src/com/android/gallery3d/glrenderer/BasicTexture.java
+src/com/android/gallery3d/glrenderer/BitmapTexture.java
+src/com/android/gallery3d/glrenderer/GLCanvas.java
+src/com/android/gallery3d/glrenderer/GLES20Canvas.java
+src/com/android/gallery3d/glrenderer/GLES20IdImpl.java
+src/com/android/gallery3d/glrenderer/GLId.java
+src/com/android/gallery3d/glrenderer/GLPaint.java
+src/com/android/gallery3d/glrenderer/RawTexture.java
+src/com/android/gallery3d/glrenderer/Texture.java
+src/com/android/gallery3d/glrenderer/UploadedTexture.java
+src/com/android/photos/BitmapRegionTileSource.java
+src/com/android/photos/views/BlockingGLTextureView.java
+src/com/android/photos/views/TiledImageRenderer.java
+src/com/android/photos/views/TiledImageView.java
+src/com/android/gallery3d/common/BitmapUtils.java
+src/com/android/launcher3/CropView.java
+src/com/android/launcher3/WallpaperCropActivity.java
+"""
+
+if len(sys.argv) != 2:
+    print "Usage: python update_sytem_wallpaper_cropper.py <framework_dir>"
+    exit()
+framework_dir = sys.argv[1] + "/packages/WallpaperCropper"
+for file_path in files.split():
+    dir = os.path.dirname(file_path)
+    dir = dir.replace("launcher3", "wallpapercropper")
+    cmd = 'cp %s %s/%s' % (file_path, framework_dir, dir)
+    print cmd
+    os.system(cmd)