Merge "Add script to push changes to system wallpaper cropper" into jb-ub-now-jetsonic
diff --git a/Android.mk b/Android.mk
index 3b1a244..3bd20a7 100644
--- a/Android.mk
+++ b/Android.mk
@@ -37,7 +37,7 @@
 LOCAL_PACKAGE_NAME := Launcher3
 #LOCAL_CERTIFICATE := shared
 
-LOCAL_OVERRIDES_PACKAGES := Home
+LOCAL_OVERRIDES_PACKAGES := Launcher2
 
 LOCAL_PROGUARD_FLAG_FILES := proguard.flags
 
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/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 c91cc7e..620ce1f 100644
--- a/res/layout/wallpaper_picker.xml
+++ b/res/layout/wallpaper_picker.xml
@@ -27,13 +27,18 @@
         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-sw/strings.xml b/res/values-sw/strings.xml
index 606a80d..d78b536 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -104,7 +104,7 @@
     <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
     <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Unda skrini zaidi za programu na folda"</string>
     <string name="workspace_cling_title" msgid="5626202359865825661">"Panga nafasi yako"</string>
-    <string name="workspace_cling_move_item" msgid="528201129978005352">"Gusa na ushikile mandharinyuma ili udhibiti mandhari, wijeti, na mipangilio."</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Gusa na ushikilie mandharinyuma ili udhibiti mandhari, wijeti, na mipangilio."</string>
     <string name="all_apps_cling_title" msgid="34929250753095858">"Chagua programu kadhaa"</string>
     <string name="all_apps_cling_add_item" msgid="400866858451850784">"Ili kuongeza programu kwenye Skrini yako Kuu, iguse na uishikilie."</string>
     <string name="folder_cling_title" msgid="3894908818693254164">"Folda hii hapa"</string>
diff --git a/src/com/android/launcher3/DragController.java b/src/com/android/launcher3/DragController.java
index 5b5c35c..5e733f0 100644
--- a/src/com/android/launcher3/DragController.java
+++ b/src/com/android/launcher3/DragController.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3;
 
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -25,14 +26,8 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.util.Log;
-import android.view.HapticFeedbackConstants;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewConfiguration;
+import android.view.*;
 import android.view.inputmethod.InputMethodManager;
-
 import com.android.launcher3.R;
 
 import java.util.ArrayList;
@@ -323,7 +318,7 @@
         }
         endDrag();
     }
-    public void onAppsRemoved(ArrayList<AppInfo> appInfos, Context context) {
+    public void onAppsRemoved(final ArrayList<String> packageNames, ArrayList<AppInfo> appInfos) {
         // Cancel the current drag if we are removing an app that we are dragging
         if (mDragObject != null) {
             Object rawDragInfo = mDragObject.dragInfo;
@@ -333,8 +328,9 @@
                     // Added null checks to prevent NPE we've seen in the wild
                     if (dragInfo != null &&
                         dragInfo.intent != null) {
-                        boolean isSameComponent =
-                                dragInfo.intent.getComponent().equals(info.componentName);
+                        ComponentName cn = dragInfo.intent.getComponent();
+                        boolean isSameComponent = cn.equals(info.componentName) ||
+                                packageNames.contains(cn.getPackageName());
                         if (isSameComponent) {
                             cancelDrag();
                             return;
diff --git a/src/com/android/launcher3/DragLayer.java b/src/com/android/launcher3/DragLayer.java
index 89f8275..159d7d9 100644
--- a/src/com/android/launcher3/DragLayer.java
+++ b/src/com/android/launcher3/DragLayer.java
@@ -480,7 +480,7 @@
     }
 
     public void animateViewIntoPosition(DragView dragView, final View child) {
-        animateViewIntoPosition(dragView, child, null);
+        animateViewIntoPosition(dragView, child, null, null);
     }
 
     public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha,
@@ -496,8 +496,8 @@
     }
 
     public void animateViewIntoPosition(DragView dragView, final View child,
-            final Runnable onFinishAnimationRunnable) {
-        animateViewIntoPosition(dragView, child, -1, onFinishAnimationRunnable, null);
+            final Runnable onFinishAnimationRunnable, View anchorView) {
+        animateViewIntoPosition(dragView, child, -1, onFinishAnimationRunnable, anchorView);
     }
 
     public void animateViewIntoPosition(DragView dragView, final View child, int duration,
@@ -645,8 +645,10 @@
                 int x = (int) (fromLeft + Math.round(((to.left - fromLeft) * motionPercent)));
                 int y = (int) (fromTop + Math.round(((to.top - fromTop) * motionPercent)));
 
-                int xPos = x - mDropView.getScrollX() + (mAnchorView != null
-                        ? (mAnchorViewInitialScrollX - mAnchorView.getScrollX()) : 0);
+                int anchorAdjust = mAnchorView == null ? 0 : (int) (mAnchorView.getScaleX() *
+                    (mAnchorViewInitialScrollX - mAnchorView.getScrollX()));
+
+                int xPos = x - mDropView.getScrollX() + anchorAdjust;
                 int yPos = y - mDropView.getScrollY();
 
                 mDropView.setTranslationX(xPos);
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 2aab68b..543b8ee 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -29,6 +29,8 @@
 import android.graphics.drawable.Drawable;
 
 import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map.Entry;
 
 /**
  * Cache of application icons.  Icons can be made from any thread.
@@ -151,10 +153,11 @@
      */
     public void flushInvalidIcons(DeviceProfile grid) {
         synchronized (mCache) {
-            for (ComponentName cn : mCache.keySet()) {
-                final CacheEntry e = mCache.get(cn);
+            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) {
-                    mCache.remove(cn);
+                    it.remove();
                 }
             }
         }
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 821c15f..835c472 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -36,6 +36,9 @@
 import org.json.*;
 
 public class InstallShortcutReceiver extends BroadcastReceiver {
+    private static final String TAG = "InstallShortcutReceiver";
+    private static final boolean DBG = false;
+
     public static final String ACTION_INSTALL_SHORTCUT =
             "com.android.launcher.action.INSTALL_SHORTCUT";
 
@@ -94,21 +97,31 @@
                 }
                 json = json.endObject();
                 SharedPreferences.Editor editor = sharedPrefs.edit();
+                if (DBG) Log.d(TAG, "Adding to APPS_PENDING_INSTALL: " + json);
                 addToStringSet(sharedPrefs, editor, APPS_PENDING_INSTALL, json.toString());
                 editor.commit();
             } catch (org.json.JSONException e) {
-                Log.d("InstallShortcutReceiver", "Exception when adding shortcut: " + e);
+                Log.d(TAG, "Exception when adding shortcut: " + e);
             }
         }
     }
 
     public static void removeFromInstallQueue(SharedPreferences sharedPrefs,
                                               ArrayList<String> packageNames) {
+        if (packageNames.isEmpty()) {
+            return;
+        }
         synchronized(sLock) {
             Set<String> strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null);
+            if (DBG) {
+                Log.d(TAG, "APPS_PENDING_INSTALL: " + strings
+                        + ", removing packages: " + packageNames);
+            }
             if (strings != null) {
                 Set<String> newStrings = new HashSet<String>(strings);
-                for (String json : newStrings) {
+                Iterator<String> newStringsIter = newStrings.iterator();
+                while (newStringsIter.hasNext()) {
+                    String json = newStringsIter.next();
                     try {
                         JSONObject object = (JSONObject) new JSONTokener(json).nextValue();
                         Intent launchIntent = Intent.parseUri(object.getString(LAUNCH_INTENT_KEY), 0);
@@ -117,12 +130,12 @@
                             pn = launchIntent.getComponent().getPackageName();
                         }
                         if (packageNames.contains(pn)) {
-                            newStrings.remove(json);
+                            newStringsIter.remove();
                         }
                     } catch (org.json.JSONException e) {
-                        Log.d("InstallShortcutReceiver", "Exception reading shortcut to remove: " + e);
+                        Log.d(TAG, "Exception reading shortcut to remove: " + e);
                     } catch (java.net.URISyntaxException e) {
-                        Log.d("InstallShortcutReceiver", "Exception reading shortcut to remove: " + e);
+                        Log.d(TAG, "Exception reading shortcut to remove: " + e);
                     }
                 }
                 sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL,
@@ -135,6 +148,7 @@
             SharedPreferences sharedPrefs) {
         synchronized(sLock) {
             Set<String> strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null);
+            if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings);
             if (strings == null) {
                 return new ArrayList<PendingInstallShortcutInfo>();
             }
@@ -167,11 +181,9 @@
                         new PendingInstallShortcutInfo(data, name, launchIntent);
                     infos.add(info);
                 } catch (org.json.JSONException e) {
-                    Log.d("InstallShortcutReceiver",
-                            "Exception reading shortcut to add: " + e);
+                    Log.d(TAG, "Exception reading shortcut to add: " + e);
                 } catch (java.net.URISyntaxException e) {
-                    Log.d("InstallShortcutReceiver",
-                            "Exception reading shortcut to add: " + e);
+                    Log.d(TAG, "Exception reading shortcut to add: " + e);
                 }
             }
             sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL, new HashSet<String>()).commit();
@@ -203,22 +215,16 @@
             return;
         }
 
+        if (DBG) Log.d(TAG, "Got INSTALL_SHORTCUT: " + data.toUri(0));
+
         Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
         if (intent == null) {
             return;
         }
         // This name is only used for comparisons and notifications, so fall back to activity name
         // if not supplied
-        String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
-        if (name == null) {
-            try {
-                PackageManager pm = context.getPackageManager();
-                ActivityInfo info = pm.getActivityInfo(intent.getComponent(), 0);
-                name = info.loadLabel(pm).toString();
-            } catch (PackageManager.NameNotFoundException nnfe) {
-                return;
-            }
-        }
+        String name = ensureValidName(context, intent,
+                data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME)).toString();
         Bitmap icon = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
         Intent.ShortcutIconResource iconResource =
             data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
@@ -304,6 +310,25 @@
                     Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
         }
         LauncherAppState app = LauncherAppState.getInstance();
-        return app.getModel().infoFromShortcutIntent(context, data, null);
+        ShortcutInfo info = app.getModel().infoFromShortcutIntent(context, data, null);
+        info.title = ensureValidName(context, launchIntent, info.title);
+        return info;
+    }
+
+    /**
+     * Ensures that we have a valid, non-null name.  If the provided name is null, we will return
+     * the application name instead.
+     */
+    private static CharSequence ensureValidName(Context context, Intent intent, CharSequence name) {
+        if (name == null) {
+            try {
+                PackageManager pm = context.getPackageManager();
+                ActivityInfo info = pm.getActivityInfo(intent.getComponent(), 0);
+                name = info.loadLabel(pm).toString();
+            } catch (PackageManager.NameNotFoundException nnfe) {
+                return "";
+            }
+        }
+        return name;
     }
 }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index b0e4968..af58f79 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -3971,26 +3971,27 @@
      * Implementation of the method from LauncherModel.Callbacks.
      */
     public void bindComponentsRemoved(final ArrayList<String> packageNames,
-                                      final ArrayList<AppInfo> appInfos,
-                                      final boolean packageRemoved) {
+                                      final ArrayList<AppInfo> appInfos) {
         Runnable r = new Runnable() {
             public void run() {
-                bindComponentsRemoved(packageNames, appInfos, packageRemoved);
+                bindComponentsRemoved(packageNames, appInfos);
             }
         };
         if (waitUntilResume(r)) {
             return;
         }
 
-        if (packageRemoved) {
+        if (!packageNames.isEmpty()) {
             mWorkspace.removeItemsByPackageName(packageNames);
-        } else {
+        }
+        if (!appInfos.isEmpty()) {
             mWorkspace.removeItemsByApplicationInfo(appInfos);
         }
 
         // Notify the drag controller
-        mDragController.onAppsRemoved(appInfos, this);
+        mDragController.onAppsRemoved(packageNames, appInfos);
 
+        // Update AllApps
         if (!AppsCustomizePagedView.DISABLE_ALL_APPS &&
                 mAppsCustomizeContent != null) {
             mAppsCustomizeContent.removeApps(appInfos);
@@ -4007,7 +4008,6 @@
                 mWidgetsAndShortcuts = null;
             }
         };
-
     public void bindPackagesUpdated(final ArrayList<Object> widgetsAndShortcuts) {
         if (waitUntilResume(mBindPackagesUpdatedRunnable, true)) {
             mWidgetsAndShortcuts = widgetsAndShortcuts;
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index 01f72a7..5d4f9c6 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -30,6 +30,7 @@
     static HashSet<Animator> sAnimators = new HashSet<Animator>();
     static Animator.AnimatorListener sEndAnimListener = new Animator.AnimatorListener() {
         public void onAnimationStart(Animator animation) {
+            sAnimators.add(animation);
         }
 
         public void onAnimationRepeat(Animator animation) {
@@ -45,7 +46,6 @@
     };
 
     public static void cancelOnDestroyActivity(Animator a) {
-        sAnimators.add(a);
         a.addListener(sEndAnimListener);
     }
 
diff --git a/src/com/android/launcher3/LauncherAppWidgetHostView.java b/src/com/android/launcher3/LauncherAppWidgetHostView.java
index 83aef1a..51a649a 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHostView.java
@@ -65,6 +65,12 @@
     }
 
     public boolean onInterceptTouchEvent(MotionEvent ev) {
+        // Just in case the previous long press hasn't been cleared, we make sure to start fresh
+        // on touch down.
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            mLongPressHelper.cancelLongPress();
+        }
+
         // Consume any touch events for ourselves after longpress is triggered
         if (mLongPressHelper.hasPerformedLongPress()) {
             mLongPressHelper.cancelLongPress();
@@ -110,13 +116,15 @@
 
     @Override
     public void onTouchComplete() {
-        mLongPressHelper.cancelLongPress();
+        if (!mLongPressHelper.hasPerformedLongPress()) {
+            // If a long press has been performed, we don't want to clear the record of that since
+            // we still may be receiving a touch up which we want to intercept
+            mLongPressHelper.cancelLongPress();
+        }
     }
 
     @Override
     public int getDescendantFocusability() {
         return ViewGroup.FOCUS_BLOCK_DESCENDANTS;
     }
-
-
 }
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 7e1442d..c746b4d 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -165,8 +165,7 @@
                                   ArrayList<AppInfo> addedApps);
         public void bindAppsUpdated(ArrayList<AppInfo> apps);
         public void bindComponentsRemoved(ArrayList<String> packageNames,
-                        ArrayList<AppInfo> appInfos,
-                        boolean matchPackageNamesOnly);
+                        ArrayList<AppInfo> appInfos);
         public void bindPackagesUpdated(ArrayList<Object> widgetsAndShortcuts);
         public void bindSearchablesChanged();
         public boolean isAllAppsButtonRank(int rank);
@@ -186,9 +185,6 @@
         mBgAllAppsList = new AllAppsList(iconCache, appFilter);
         mIconCache = iconCache;
 
-        mDefaultIcon = Utilities.createIconBitmap(
-                mIconCache.getFullResDefaultActivityIcon(), context);
-
         final Resources res = context.getResources();
         Configuration config = res.getConfiguration();
         mPreviousConfigMcc = config.mcc;
@@ -400,6 +396,11 @@
     }
 
     public Bitmap getFallbackIcon() {
+        if (mDefaultIcon == null) {
+            final Context context = LauncherAppState.getInstance().getContext();
+            mDefaultIcon = Utilities.createIconBitmap(
+                    mIconCache.getFullResDefaultActivityIcon(), context);
+        }
         return Bitmap.createBitmap(mDefaultIcon);
     }
 
@@ -2551,43 +2552,47 @@
                     }
                 });
             }
-            // If a package has been removed, or an app has been removed as a result of
-            // an update (for example), make the removed callback.
-            if (mOp == OP_REMOVE || !removedApps.isEmpty()) {
-                final boolean packageRemoved = (mOp == OP_REMOVE);
-                final ArrayList<String> removedPackageNames =
-                        new ArrayList<String>(Arrays.asList(packages));
 
-                // Update the launcher db to reflect the removal of apps
-                if (packageRemoved) {
-                    for (String pn : removedPackageNames) {
-                        ArrayList<ItemInfo> infos = getItemInfoForPackageName(pn);
-                        for (ItemInfo i : infos) {
-                            deleteItemFromDatabase(context, i);
-                        }
-                    }
-
-                    // Remove any queued items from the install queue
-                    String spKey = LauncherAppState.getSharedPreferencesKey();
-                    SharedPreferences sp =
-                            context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
-                    InstallShortcutReceiver.removeFromInstallQueue(sp, removedPackageNames);
-                } else {
-                    for (AppInfo a : removedApps) {
-                        ArrayList<ItemInfo> infos =
-                                getItemInfoForComponentName(a.componentName);
-                        for (ItemInfo i : infos) {
-                            deleteItemFromDatabase(context, i);
-                        }
+            final ArrayList<String> removedPackageNames =
+                    new ArrayList<String>();
+            if (mOp == OP_REMOVE) {
+                // Mark all packages in the broadcast to be removed
+                removedPackageNames.addAll(Arrays.asList(packages));
+            } else if (mOp == OP_UPDATE) {
+                // Mark disabled packages in the broadcast to be removed
+                final PackageManager pm = context.getPackageManager();
+                for (int i=0; i<N; i++) {
+                    if (isPackageDisabled(pm, packages[i])) {
+                        removedPackageNames.add(packages[i]);
                     }
                 }
-
+            }
+            // Remove all the components associated with this package
+            for (String pn : removedPackageNames) {
+                ArrayList<ItemInfo> infos = getItemInfoForPackageName(pn);
+                for (ItemInfo i : infos) {
+                    deleteItemFromDatabase(context, i);
+                }
+            }
+            // Remove all the specific components
+            for (AppInfo a : removedApps) {
+                ArrayList<ItemInfo> infos = getItemInfoForComponentName(a.componentName);
+                for (ItemInfo i : infos) {
+                    deleteItemFromDatabase(context, i);
+                }
+            }
+            if (!removedPackageNames.isEmpty() || !removedApps.isEmpty()) {
+                // Remove any queued items from the install queue
+                String spKey = LauncherAppState.getSharedPreferencesKey();
+                SharedPreferences sp =
+                        context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
+                InstallShortcutReceiver.removeFromInstallQueue(sp, removedPackageNames);
+                // Call the components-removed callback
                 mHandler.post(new Runnable() {
                     public void run() {
                         Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
                         if (callbacks == cb && cb != null) {
-                            callbacks.bindComponentsRemoved(removedPackageNames,
-                                    removedApps, packageRemoved);
+                            callbacks.bindComponentsRemoved(removedPackageNames, removedApps);
                         }
                     }
                 });
@@ -2629,19 +2634,26 @@
         return widgetsAndShortcuts;
     }
 
+    private boolean isPackageDisabled(PackageManager pm, String packageName) {
+        try {
+            PackageInfo pi = pm.getPackageInfo(packageName, 0);
+            return !pi.applicationInfo.enabled;
+        } catch (NameNotFoundException e) {
+            // Fall through
+        }
+        return false;
+    }
     private boolean isValidPackageComponent(PackageManager pm, ComponentName cn) {
         if (cn == null) {
             return false;
         }
+        if (isPackageDisabled(pm, cn.getPackageName())) {
+            return false;
+        }
 
         try {
-            // Skip if the application is disabled
-            PackageInfo pi = pm.getPackageInfo(cn.getPackageName(), 0);
-            if (!pi.applicationInfo.enabled) {
-                return false;
-            }
-
             // Check the activity
+            PackageInfo pi = pm.getPackageInfo(cn.getPackageName(), 0);
             return (pm.getActivityInfo(cn, 0) != null);
         } catch (NameNotFoundException e) {
             return false;
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index e982985..e724063 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -268,8 +268,6 @@
 
     protected final Rect mInsets = new Rect();
 
-    protected int mFirstChildLeft;
-
     public interface PageSwitchListener {
         void onPageSwitch(View newPage, int newPageIndex);
     }
@@ -899,10 +897,6 @@
         requestLayout();
     }
 
-    protected int getFirstChildLeft() {
-        return mFirstChildLeft;
-    }
-
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         if (!mIsDataReady || getChildCount() == 0) {
@@ -928,7 +922,7 @@
 
         int verticalPadding = getPaddingTop() + getPaddingBottom();
 
-        int childLeft = mFirstChildLeft = offsetX + (screenWidth - getChildWidth(startIndex)) / 2;
+        int childLeft = offsetX + (screenWidth - getChildWidth(startIndex)) / 2;
         if (mPageScrolls == null || getChildCount() != mChildCountOnLastLayout) {
             mPageScrolls = new int[getChildCount()];
         }
diff --git a/src/com/android/launcher3/SavedWallpaperImages.java b/src/com/android/launcher3/SavedWallpaperImages.java
index 8d5b005..086d085 100644
--- a/src/com/android/launcher3/SavedWallpaperImages.java
+++ b/src/com/android/launcher3/SavedWallpaperImages.java
@@ -60,12 +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());
-            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);
         }
         @Override
         public void onSave(WallpaperPickerActivity a) {
diff --git a/src/com/android/launcher3/SearchDropTargetBar.java b/src/com/android/launcher3/SearchDropTargetBar.java
index 7a61479..435dbda 100644
--- a/src/com/android/launcher3/SearchDropTargetBar.java
+++ b/src/com/android/launcher3/SearchDropTargetBar.java
@@ -138,6 +138,8 @@
      * Shows and hides the search bar.
      */
     public void showSearchBar(boolean animated) {
+        boolean needToCancelOngoingAnimation = mQSBSearchBarAnim.isRunning() && !animated;
+        if (!mIsSearchBarHidden && !needToCancelOngoingAnimation) return;
         if (animated) {
             prepareStartAnimation(mQSBSearchBar);
             mQSBSearchBarAnim.reverse();
@@ -152,6 +154,8 @@
         mIsSearchBarHidden = false;
     }
     public void hideSearchBar(boolean animated) {
+        boolean needToCancelOngoingAnimation = mQSBSearchBarAnim.isRunning() && !animated;
+        if (mIsSearchBarHidden && !needToCancelOngoingAnimation) return;
         if (animated) {
             prepareStartAnimation(mQSBSearchBar);
             mQSBSearchBarAnim.start();
diff --git a/src/com/android/launcher3/WallpaperCropActivity.java b/src/com/android/launcher3/WallpaperCropActivity.java
index 30ec340..29e8c97 100644
--- a/src/com/android/launcher3/WallpaperCropActivity.java
+++ b/src/com/android/launcher3/WallpaperCropActivity.java
@@ -37,7 +37,6 @@
 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;
@@ -96,9 +95,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 +107,46 @@
                         cropImageAndSetWallpaper(imageUri, null, finishActivityWhenDone);
                     }
                 });
+
+        // Load image in background
+        setCropViewTileSource(
+                new BitmapRegionTileSource.UriBitmapSource(this, imageUri, 1024), true, false);
+    }
+
+    public void setCropViewTileSource(final BitmapRegionTileSource.BitmapSource bitmapSource,
+            final boolean touchEnabled, final boolean moveToLeft) {
+        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);
+                    mCropView.setTileSource(
+                            new BitmapRegionTileSource(context, bitmapSource), null);
+                    mCropView.setTouchEnabled(touchEnabled);
+                    if (moveToLeft) {
+                        mCropView.moveToLeft();
+                    }
+                }
+            }
+        };
+        // 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() {
diff --git a/src/com/android/launcher3/WallpaperPickerActivity.java b/src/com/android/launcher3/WallpaperPickerActivity.java
index e71a26b..9c6ee6e 100644
--- a/src/com/android/launcher3/WallpaperPickerActivity.java
+++ b/src/com/android/launcher3/WallpaperPickerActivity.java
@@ -31,7 +31,9 @@
 import android.database.DataSetObserver;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
 import android.graphics.Matrix;
+import android.graphics.Paint;
 import android.graphics.Point;
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
@@ -40,6 +42,8 @@
 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;
 import android.util.Log;
@@ -51,10 +55,10 @@
 import android.view.MenuItem;
 import android.view.View;
 import android.view.View.OnClickListener;
+import android.view.View.OnLayoutChangeListener;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
-import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.DecelerateInterpolator;
 import android.widget.BaseAdapter;
@@ -64,14 +68,11 @@
 import android.widget.LinearLayout;
 import android.widget.ListAdapter;
 
-import com.android.gallery3d.exif.ExifInterface;
 import com.android.photos.BitmapRegionTileSource;
 
-import java.io.BufferedInputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.InputStream;
 import java.util.ArrayList;
 
 public class WallpaperPickerActivity extends WallpaperCropActivity {
@@ -81,6 +82,7 @@
     public static final int PICK_WALLPAPER_THIRD_PARTY_ACTIVITY = 6;
     public static final int PICK_LIVE_WALLPAPER = 7;
     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 boolean mIgnoreNextTap;
@@ -88,6 +90,7 @@
 
     private LinearLayout mWallpapersView;
     private View mWallpaperStrip;
+    private ImageView mDefaultWallpaperView;
 
     private ActionMode.Callback mActionModeCallback;
     private ActionMode mActionMode;
@@ -131,10 +134,8 @@
         }
         @Override
         public void onClick(WallpaperPickerActivity a) {
-            CropView v = a.getCropView();
-            int rotation = WallpaperCropActivity.getRotationFromExif(a, mUri);
-            v.setTileSource(new BitmapRegionTileSource(a, mUri, 1024, rotation), null);
-            v.setTouchEnabled(true);
+            a.setCropViewTileSource(
+                    new BitmapRegionTileSource.UriBitmapSource(a, mUri, 1024), true, false);
         }
         @Override
         public void onSave(final WallpaperPickerActivity a) {
@@ -172,10 +173,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, 1024);
+            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());
@@ -200,6 +203,42 @@
         }
     }
 
+    public static class DefaultWallpaperInfo extends WallpaperTileInfo {
+        public Drawable mThumb;
+        public DefaultWallpaperInfo(Drawable thumb) {
+            mThumb = thumb;
+        }
+        @Override
+        public void onClick(WallpaperPickerActivity a) {
+            a.getCropView().setTouchEnabled(false);
+            ImageView defaultWallpaperView = a.getDefaultWallpaperView();
+            defaultWallpaperView.setVisibility(View.VISIBLE);
+            Drawable defaultWallpaper = WallpaperManager.getInstance(a).getBuiltInDrawable(
+                    defaultWallpaperView.getWidth(), defaultWallpaperView.getHeight(),
+                    false, 0.5f, 0.5f);
+            if (defaultWallpaper != null) {
+                defaultWallpaperView.setBackgroundDrawable(defaultWallpaper);
+            }
+        }
+        @Override
+        public void onSave(WallpaperPickerActivity a) {
+            try {
+                WallpaperManager.getInstance(a).clear();
+            } catch (IOException e) {
+                Log.w("Setting wallpaper to default threw exception", e);
+            }
+            a.finish();
+        }
+        @Override
+        public boolean isSelectable() {
+            return true;
+        }
+        @Override
+        public boolean isNamelessWallpaper() {
+            return true;
+        }
+    }
+
     public void setWallpaperStripYOffset(float offset) {
         mWallpaperStrip.setPadding(0, 0, 0, (int) offset);
     }
@@ -209,6 +248,7 @@
         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;
@@ -305,12 +345,12 @@
         ArrayList<ResourceWallpaperInfo> wallpapers = findBundledWallpapers();
         mWallpapersView = (LinearLayout) findViewById(R.id.wallpaper_list);
         BuiltInWallpapersAdapter ia = new BuiltInWallpapersAdapter(this, wallpapers);
-        populateWallpapersFromAdapter(mWallpapersView, ia, false, true);
+        populateWallpapersFromAdapter(mWallpapersView, ia, false);
 
         // Populate the saved wallpapers
         mSavedImages = new SavedWallpaperImages(this);
         mSavedImages.loadThumbnailsAndImageIdList();
-        populateWallpapersFromAdapter(mWallpapersView, mSavedImages, true, true);
+        populateWallpapersFromAdapter(mWallpapersView, mSavedImages, true);
 
         // Populate the live wallpapers
         final LinearLayout liveWallpapersView =
@@ -319,7 +359,7 @@
         a.registerDataSetObserver(new DataSetObserver() {
             public void onChanged() {
                 liveWallpapersView.removeAllViews();
-                populateWallpapersFromAdapter(liveWallpapersView, a, false, false);
+                populateWallpapersFromAdapter(liveWallpapersView, a, false);
                 initializeScrollForRtl();
                 updateTileIndices();
             }
@@ -330,7 +370,7 @@
                 (LinearLayout) findViewById(R.id.third_party_wallpaper_list);
         final ThirdPartyWallpaperPickerListAdapter ta =
                 new ThirdPartyWallpaperPickerListAdapter(this);
-        populateWallpapersFromAdapter(thirdPartyWallpapersView, ta, false, false);
+        populateWallpapersFromAdapter(thirdPartyWallpapersView, ta, false);
 
         // Add a tile for the Gallery
         LinearLayout masterWallpaperList = (LinearLayout) findViewById(R.id.master_wallpaper_list);
@@ -354,7 +394,33 @@
         pickImageTile.setTag(pickImageInfo);
         pickImageInfo.setView(pickImageTile);
         pickImageTile.setOnClickListener(mThumbnailOnClickListener);
-        pickImageInfo.setView(pickImageTile);
+
+        // Add a tile for the default wallpaper
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+            DefaultWallpaperInfo defaultWallpaperInfo = getDefaultWallpaper();
+            FrameLayout defaultWallpaperTile = (FrameLayout) createImageTileView(
+                    getLayoutInflater(), 0, null, mWallpapersView, defaultWallpaperInfo.mThumb);
+            setWallpaperItemPaddingToZero(defaultWallpaperTile);
+            defaultWallpaperTile.setTag(defaultWallpaperInfo);
+            mWallpapersView.addView(defaultWallpaperTile, 0);
+            defaultWallpaperTile.setOnClickListener(mThumbnailOnClickListener);
+            defaultWallpaperInfo.setView(defaultWallpaperTile);
+        }
+
+        // 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() {
+            @Override
+            public void onLayoutChange(View v, int left, int top, int right, int bottom,
+                    int oldLeft, int oldTop, int oldRight, int oldBottom) {
+                if ((right - left) > 0 && (bottom - top) > 0) {
+                    if (mWallpapersView.getChildCount() > 0) {
+                        mThumbnailOnClickListener.onClick(mWallpapersView.getChildAt(0));
+                    }
+                    v.removeOnLayoutChangeListener(this);
+                }
+            }
+        });
 
         updateTileIndices();
 
@@ -461,6 +527,12 @@
             }
         };
     }
+    @Override
+    public void setCropViewTileSource(final BitmapRegionTileSource.BitmapSource bitmapSource,
+            final boolean touchEnabled, boolean moveToLeft) {
+        getDefaultWallpaperView().setVisibility(View.INVISIBLE);
+        super.setCropViewTileSource(bitmapSource, touchEnabled, moveToLeft);
+    }
 
     private void initializeScrollForRtl() {
         final HorizontalScrollView scroll =
@@ -520,7 +592,7 @@
     }
 
     private void populateWallpapersFromAdapter(ViewGroup parent, BaseAdapter adapter,
-            boolean addLongPressHandler, boolean selectFirstTile) {
+            boolean addLongPressHandler) {
         for (int i = 0; i < adapter.getCount(); i++) {
             FrameLayout thumbnail = (FrameLayout) adapter.getView(i, null, parent);
             parent.addView(thumbnail, i);
@@ -531,9 +603,6 @@
                 addLongPressHandler(thumbnail);
             }
             thumbnail.setOnClickListener(mThumbnailOnClickListener);
-            if (i == 0 && selectFirstTile) {
-                mThumbnailOnClickListener.onClick(thumbnail);
-            }
         }
     }
 
@@ -623,26 +692,34 @@
         }
     }
 
-    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().
                 inflate(R.layout.wallpaper_picker_item, mWallpapersView, false);
         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);
@@ -700,18 +777,35 @@
         }
 
         // Add an entry for the default wallpaper (stored in system resources)
-        ResourceWallpaperInfo defaultWallpaperInfo = getDefaultWallpaperInfo();
-        if (defaultWallpaperInfo != null) {
-            bundledWallpapers.add(0, defaultWallpaperInfo);
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
+            ResourceWallpaperInfo defaultWallpaperInfo = getPreKKDefaultWallpaperInfo();
+            if (defaultWallpaperInfo != null) {
+                bundledWallpapers.add(0, defaultWallpaperInfo);
+            }
         }
         return bundledWallpapers;
     }
 
-    private ResourceWallpaperInfo getDefaultWallpaperInfo() {
+    private boolean writeImageToFileAsJpeg(File f, Bitmap b) {
+        try {
+            f.createNewFile();
+            FileOutputStream thumbFileStream =
+                    openFileOutput(f.getName(), Context.MODE_PRIVATE);
+            b.compress(Bitmap.CompressFormat.JPEG, 95, thumbFileStream);
+            thumbFileStream.close();
+            return true;
+        } catch (IOException e) {
+            Log.e(TAG, "Error while writing bitmap to file " + e);
+            f.delete();
+        }
+        return false;
+    }
+
+    private ResourceWallpaperInfo getPreKKDefaultWallpaperInfo() {
         Resources sysRes = Resources.getSystem();
         int resId = sysRes.getIdentifier("default_wallpaper", "drawable", "android");
 
-        File defaultThumbFile = new File(getFilesDir(), "default_thumb.jpg");
+        File defaultThumbFile = new File(getFilesDir(), DEFAULT_WALLPAPER_THUMBNAIL_FILENAME);
         Bitmap thumb = null;
         boolean defaultWallpaperExists = false;
         if (defaultThumbFile.exists()) {
@@ -724,17 +818,7 @@
             thumb = createThumbnail(
                     defaultThumbSize, this, null, null, sysRes, resId, rotation, false);
             if (thumb != null) {
-                try {
-                    defaultThumbFile.createNewFile();
-                    FileOutputStream thumbFileStream =
-                            openFileOutput(defaultThumbFile.getName(), Context.MODE_PRIVATE);
-                    thumb.compress(Bitmap.CompressFormat.JPEG, 95, thumbFileStream);
-                    thumbFileStream.close();
-                    defaultWallpaperExists = true;
-                } catch (IOException e) {
-                    Log.e(TAG, "Error while writing default wallpaper thumbnail to file " + e);
-                    defaultThumbFile.delete();
-                }
+                defaultWallpaperExists = writeImageToFileAsJpeg(defaultThumbFile, thumb);
             }
         }
         if (defaultWallpaperExists) {
@@ -743,6 +827,37 @@
         return null;
     }
 
+    private DefaultWallpaperInfo getDefaultWallpaper() {
+        File defaultThumbFile = new File(getFilesDir(), DEFAULT_WALLPAPER_THUMBNAIL_FILENAME);
+        Bitmap thumb = null;
+        boolean defaultWallpaperExists = false;
+        if (defaultThumbFile.exists()) {
+            thumb = BitmapFactory.decodeFile(defaultThumbFile.getAbsolutePath());
+            defaultWallpaperExists = true;
+        } else {
+            Resources res = getResources();
+            Point defaultThumbSize = getDefaultThumbnailSize(res);
+            Paint p = new Paint();
+            p.setFilterBitmap(true);
+            Drawable wallpaperDrawable = WallpaperManager.getInstance(this).getBuiltInDrawable(
+                    defaultThumbSize.x, defaultThumbSize.y, true, 0.5f, 0.5f);
+            if (wallpaperDrawable != null) {
+                thumb = Bitmap.createBitmap(
+                        defaultThumbSize.x, defaultThumbSize.y, Bitmap.Config.ARGB_8888);
+                Canvas c = new Canvas(thumb);
+                wallpaperDrawable.draw(c);
+                c.setBitmap(null);
+            }
+            if (thumb != null) {
+                defaultWallpaperExists = writeImageToFileAsJpeg(defaultThumbFile, thumb);
+            }
+        }
+        if (defaultWallpaperExists) {
+            return new DefaultWallpaperInfo(new BitmapDrawable(thumb));
+        }
+        return null;
+    }
+
     public Pair<ApplicationInfo, Integer> getWallpaperArrayResourceId() {
         // Context.getPackageName() may return the "original" package name,
         // com.android.launcher3; Resources needs the real package name,
@@ -784,6 +899,10 @@
         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 908a61e..e9d41d5 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -1199,10 +1199,8 @@
                 // TODO: do different behavior if it's  a live wallpaper?
                 // Sometimes the left parameter of the pages is animated during a layout transition;
                 // this parameter offsets it to keep the wallpaper from animating as well
-                int offsetForLayoutTransitionAnimation = isLayoutRtl() ?
-                        getPageAt(getChildCount() - 1).getLeft() - getFirstChildLeft() : 0;
                 int adjustedScroll =
-                        getScrollX() - firstPageScrollX - offsetForLayoutTransitionAnimation;
+                        getScrollX() - firstPageScrollX - getLayoutTransitionOffsetForPage(0);
                 float offset = Math.min(1, adjustedScroll / (float) scrollRange);
                 offset = Math.max(0, offset);
                 // Don't use up all the wallpaper parallax until you have at least
@@ -3654,7 +3652,7 @@
                 // the correct final location.
                 setFinalTransitionTransform(cellLayout);
                 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view,
-                        exitSpringLoadedRunnable);
+                        exitSpringLoadedRunnable, this);
                 resetTransitionTransform(cellLayout);
             }
         }
@@ -4087,6 +4085,7 @@
             }
         }
         mRestoredPages.clear();
+        mSavedStates = null;
     }
 
     @Override
@@ -4383,29 +4382,45 @@
         stripEmptyScreens();
     }
 
+    private void updateShortcut(HashMap<ComponentName, AppInfo> appsMap, ItemInfo info,
+                                View child) {
+        ComponentName cn = info.getIntent().getComponent();
+        if (cn != null) {
+            AppInfo appInfo = appsMap.get(info.getIntent().getComponent());
+            if ((appInfo != null) && LauncherModel.isShortcutInfoUpdateable(info)) {
+                ShortcutInfo shortcutInfo = (ShortcutInfo) info;
+                BubbleTextView shortcut = (BubbleTextView) child;
+                shortcutInfo.updateIcon(mIconCache);
+                shortcutInfo.title = appInfo.title.toString();
+                shortcut.applyFromShortcutInfo(shortcutInfo, mIconCache);
+            }
+        }
+    }
+
     void updateShortcuts(ArrayList<AppInfo> apps) {
+        // Create a map of the apps to test against
+        final HashMap<ComponentName, AppInfo> appsMap = new HashMap<ComponentName, AppInfo>();
+        for (AppInfo ai : apps) {
+            appsMap.put(ai.componentName, ai);
+        }
+
         ArrayList<ShortcutAndWidgetContainer> childrenLayouts = getAllShortcutAndWidgetContainers();
         for (ShortcutAndWidgetContainer layout: childrenLayouts) {
-            int childCount = layout.getChildCount();
-            for (int j = 0; j < childCount; j++) {
-                final View view = layout.getChildAt(j);
-                Object tag = view.getTag();
-
-                if (LauncherModel.isShortcutInfoUpdateable((ItemInfo) tag)) {
-                    ShortcutInfo info = (ShortcutInfo) tag;
-
-                    final Intent intent = info.intent;
-                    final ComponentName name = intent.getComponent();
-                    final int appCount = apps.size();
-                    for (int k = 0; k < appCount; k++) {
-                        AppInfo app = apps.get(k);
-                        if (app.componentName.equals(name)) {
-                            BubbleTextView shortcut = (BubbleTextView) view;
-                            info.updateIcon(mIconCache);
-                            info.title = app.title.toString();
-                            shortcut.applyFromShortcutInfo(info, mIconCache);
-                        }
+            // Update all the children shortcuts
+            final HashMap<ItemInfo, View> children = new HashMap<ItemInfo, View>();
+            for (int j = 0; j < layout.getChildCount(); j++) {
+                View v = layout.getChildAt(j);
+                ItemInfo info = (ItemInfo) v.getTag();
+                if (info instanceof FolderInfo && v instanceof FolderIcon) {
+                    FolderIcon folder = (FolderIcon) v;
+                    ArrayList<View> folderChildren = folder.getFolder().getItemsInReadingOrder();
+                    for (View fv : folderChildren) {
+                        info = (ItemInfo) fv.getTag();
+                        updateShortcut(appsMap, info, fv);
                     }
+                    folder.invalidate();
+                } else if (info instanceof ShortcutInfo) {
+                    updateShortcut(appsMap, info, v);
                 }
             }
         }
diff --git a/src/com/android/photos/BitmapRegionTileSource.java b/src/com/android/photos/BitmapRegionTileSource.java
index 5f64018..74284b2 100644
--- a/src/com/android/photos/BitmapRegionTileSource.java
+++ b/src/com/android/photos/BitmapRegionTileSource.java
@@ -31,11 +31,13 @@
 import android.util.Log;
 
 import com.android.gallery3d.common.BitmapUtils;
+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;
 
@@ -55,6 +57,175 @@
     // 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 abstract class BitmapSource {
+        private BitmapRegionDecoder mDecoder;
+        private Bitmap mPreview;
+        private int mPreviewSize;
+        private int mRotation;
+        public BitmapSource(int previewSize) {
+            mPreviewSize = previewSize;
+        }
+        public void 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();
+            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);
+            }
+        }
+
+        public BitmapRegionDecoder 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 BitmapRegionDecoder 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 BitmapRegionDecoder loadBitmapRegionDecoder() {
+            try {
+                return BitmapRegionDecoder.newInstance(mPath, true);
+            } catch (IOException e) {
+                Log.w("BitmapRegionTileSource", "getting decoder failed", e);
+                return null;
+            }
+        }
+        @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 BitmapRegionDecoder loadBitmapRegionDecoder() {
+            try {
+                return BitmapRegionDecoder.newInstance(regenerateInputStream(), true);
+            } 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 {
+                return BitmapFactory.decodeStream(regenerateInputStream(), null, options);
+            } catch (FileNotFoundException e) {
+                Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
+                return null;
+            }
+        }
+        @Override
+        public boolean readExif(ExifInterface ei) {
+            try {
+                ei.readExif(regenerateInputStream());
+                return true;
+            } catch (FileNotFoundException e) {
+                Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
+                return false;
+            } catch (IOException e) {
+                Log.e("BitmapRegionTileSource", "Failure while reading URI " + mUri, e);
+                return false;
+            }
+        }
+    }
+
+    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 BitmapRegionDecoder loadBitmapRegionDecoder() {
+            try {
+                return BitmapRegionDecoder.newInstance(regenerateInputStream(), true);
+            } catch (IOException e) {
+                Log.e("BitmapRegionTileSource", "Error reading resource", e);
+                return null;
+            }
+        }
+        @Override
+        public Bitmap loadPreviewBitmap(BitmapFactory.Options options) {
+            return BitmapFactory.decodeResource(mRes, mResId, options);
+        }
+        @Override
+        public boolean readExif(ExifInterface ei) {
+            try {
+                ei.readExif(regenerateInputStream());
+                return true;
+            } catch (IOException e) {
+                Log.e("BitmapRegionTileSource", "Error reading resource", e);
+                return false;
+            }
+        }
+    }
+
     BitmapRegionDecoder mDecoder;
     int mWidth;
     int mHeight;
@@ -68,50 +239,23 @@
     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);
-            }
-            mWidth = mDecoder.getWidth();
-            mHeight = mDecoder.getHeight();
-        } catch (IOException e) {
-            Log.w("BitmapRegionTileSource", "ctor failed", e);
-        }
+        mRotation = source.getRotation();
+        mDecoder = source.getBitmapRegionDecoder();
+        mWidth = mDecoder.getWidth();
+        mHeight = mDecoder.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(res, context, path, uri, resId, previewSize);
+            Bitmap preview = decodePreview(source, previewSize);
             if (preview.getWidth() <= GL_SIZE_LIMIT && preview.getHeight() <= GL_SIZE_LIMIT) {
                 mPreview = new BitmapTexture(preview);
             } else {
@@ -215,33 +359,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);