Merge "Fix issue where overview panel was taking clicks because it was visible (issue 10732462)" into jb-ub-now-indigo-rose
diff --git a/Android.mk b/Android.mk
index 016d151..10c9f24 100644
--- a/Android.mk
+++ b/Android.mk
@@ -21,7 +21,13 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v13
 
-LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src)
+LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src) \
+    $(call all-proto-files-under, protos)
+
+LOCAL_PROTOC_OPTIMIZE_TYPE := nano
+
+LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/protos
+
 LOCAL_SDK_VERSION := 17
 
 LOCAL_PACKAGE_NAME := Launcher3
diff --git a/protos/backup.proto b/protos/backup.proto
new file mode 100644
index 0000000..3780bc5
--- /dev/null
+++ b/protos/backup.proto
@@ -0,0 +1,76 @@
+/*
+ * 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 launcher_backup;
+
+option java_package = "com.android.launcher3.backup";
+option java_outer_classname = "BackupProtos";
+
+message Key {
+  enum Type {
+    FAVORITE = 1;
+    SCREEN = 2;
+    IMAGE = 3;
+  }
+  required Type type = 1;
+  optional string name = 2;  // keep this short
+  optional int64 id = 3;
+  optional int64 checksum = 4;
+}
+
+message CheckedMessage {
+  required bytes payload = 1;
+  required int64 checksum = 2;
+}
+
+message Journal {
+  required int32 app_version = 1;
+  required int64 t = 2;
+  optional int64 bytes = 3;
+  optional int32 rows = 4;
+  repeated Key key = 5;
+}
+
+message Favorite {
+  required int64 id = 1;
+  required int32 itemType = 2;
+  optional string title = 3;
+  optional int32 container = 4;
+  optional int32 screen = 5;
+  optional int32 cellX = 6;
+  optional int32 cellY = 7;
+  optional int32 spanX = 8;
+  optional int32 spanY = 9;
+  optional int32 displayMode = 10;
+  optional int32 appWidgetId = 11;
+  optional string appWidgetProvider = 12;
+  optional string intent = 13;
+  optional string uri = 14;
+  optional int32 iconType = 15;
+  optional string iconPackage = 16;
+  optional string iconResource = 17;
+  optional bytes icon = 18;
+ }
+
+message Screen {
+  required int64 id = 1;
+  optional int32 rank = 2;
+ }
+
+message Resource {
+  required int32 dpi = 2;
+  required bytes data = 3;
+ }
diff --git a/res/layout-land/first_run_cling.xml b/res/layout-land/first_run_cling.xml
index f827380..3b21e14 100644
--- a/res/layout-land/first_run_cling.xml
+++ b/res/layout-land/first_run_cling.xml
@@ -34,9 +34,10 @@
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_gravity="center_horizontal"
+                android:layout_marginBottom="10dp"
                 android:text="@string/first_run_cling_title"
-                android:textColor="#49C0EC"
-                android:textSize="32sp" />
+                android:textColor="#FFFFFFFF"
+                android:textSize="30sp" />
             <TextView
                 style="@style/ClingAltTitleText"
                 android:layout_width="wrap_content"
@@ -47,22 +48,28 @@
         </LinearLayout>
         <TextView
             style="@style/ClingHintText"
+            android:id="@+id/search_bar_hint"
             android:layout_width="160dp"
             android:layout_height="wrap_content"
             android:layout_gravity="top|end"
             android:layout_marginEnd="10dp"
             android:layout_marginTop="80dp"
-            android:text="@string/first_run_cling_search_bar_hint"
-            android:visibility="gone" />
+            android:visibility="gone"
+            android:drawableTop="@drawable/cling_arrow_up"
+            android:drawablePadding="5dp"
+            android:text="@string/first_run_cling_search_bar_hint" />
         <TextView
             style="@style/ClingHintText"
+            android:id="@+id/custom_content_hint"
             android:layout_width="160dp"
             android:layout_height="wrap_content"
             android:layout_gravity="top"
             android:layout_marginStart="10dp"
             android:layout_marginTop="100dp"
-            android:text="@string/first_run_cling_custom_content_hint"
-            android:visibility="gone" />
+            android:visibility="gone"
+            android:drawableStart="@drawable/cling_arrow_left"
+            android:drawablePadding="10dp"
+            android:text="@string/first_run_cling_custom_content_hint" />
         <TextView
             style="@style/ClingHintText"
             android:layout_width="160dp"
@@ -70,8 +77,9 @@
             android:layout_gravity="bottom|end"
             android:layout_marginEnd="10dp"
             android:layout_marginBottom="100dp"
-            android:text="@string/first_run_cling_create_screens_hint"
-            android:visibility="gone" />
+            android:drawableEnd="@drawable/cling_arrow_right"
+            android:drawablePadding="5dp"
+            android:text="@string/first_run_cling_create_screens_hint" />
     </FrameLayout>
     <Button
         style="@style/ClingButton"
diff --git a/res/layout-port/first_run_cling.xml b/res/layout-port/first_run_cling.xml
index cdc49b9..3b21e14 100644
--- a/res/layout-port/first_run_cling.xml
+++ b/res/layout-port/first_run_cling.xml
@@ -36,7 +36,7 @@
                 android:layout_gravity="center_horizontal"
                 android:layout_marginBottom="10dp"
                 android:text="@string/first_run_cling_title"
-                android:textColor="#49C0EC"
+                android:textColor="#FFFFFFFF"
                 android:textSize="30sp" />
             <TextView
                 style="@style/ClingAltTitleText"
@@ -48,19 +48,27 @@
         </LinearLayout>
         <TextView
             style="@style/ClingHintText"
+            android:id="@+id/search_bar_hint"
             android:layout_width="160dp"
             android:layout_height="wrap_content"
             android:layout_gravity="top|end"
             android:layout_marginEnd="10dp"
             android:layout_marginTop="80dp"
+            android:visibility="gone"
+            android:drawableTop="@drawable/cling_arrow_up"
+            android:drawablePadding="5dp"
             android:text="@string/first_run_cling_search_bar_hint" />
         <TextView
             style="@style/ClingHintText"
+            android:id="@+id/custom_content_hint"
             android:layout_width="160dp"
             android:layout_height="wrap_content"
             android:layout_gravity="top"
             android:layout_marginStart="10dp"
             android:layout_marginTop="100dp"
+            android:visibility="gone"
+            android:drawableStart="@drawable/cling_arrow_left"
+            android:drawablePadding="10dp"
             android:text="@string/first_run_cling_custom_content_hint" />
         <TextView
             style="@style/ClingHintText"
@@ -70,6 +78,7 @@
             android:layout_marginEnd="10dp"
             android:layout_marginBottom="100dp"
             android:drawableEnd="@drawable/cling_arrow_right"
+            android:drawablePadding="5dp"
             android:text="@string/first_run_cling_create_screens_hint" />
     </FrameLayout>
     <Button
diff --git a/res/layout/apps_customize_pane.xml b/res/layout/apps_customize_pane.xml
index b01add9..e488601 100644
--- a/res/layout/apps_customize_pane.xml
+++ b/res/layout/apps_customize_pane.xml
@@ -16,7 +16,7 @@
 <com.android.launcher3.AppsCustomizeTabHost
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
-    android:background="#FF000000">
+    android:background="#80FFFFFF">
     <LinearLayout
         android:id="@+id/apps_customize_content"
         android:orientation="vertical"
diff --git a/res/layout/apps_customize_widget.xml b/res/layout/apps_customize_widget.xml
index ad677e9..f2d2342 100644
--- a/res/layout/apps_customize_widget.xml
+++ b/res/layout/apps_customize_widget.xml
@@ -35,8 +35,7 @@
         android:paddingStart="@dimen/app_widget_preview_padding_left"
         android:paddingEnd="@dimen/app_widget_preview_padding_right"
         android:scaleType="matrix"
-        android:background="@drawable/widget_container_holo" />
-
+        android:background="@drawable/screenpanel" />
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
@@ -57,7 +56,10 @@
 
             android:textColor="#FFFFFFFF"
             android:textSize="13sp"
-            android:textAlignment="viewStart" />
+            android:textAlignment="viewStart"
+            android:fontFamily="sans-serif-condensed"
+            android:shadowRadius="2.0"
+            android:shadowColor="#B0000000" />
 
         <!-- The original dimensions of the widget (can't be the same text as above due to different
              style. -->
@@ -70,8 +72,11 @@
             android:layout_weight="0"
             android:gravity="start"
 
-            android:textColor="#FF555555"
-            android:textSize="12sp" />
+            android:textColor="#FFAAAAAA"
+            android:textSize="12sp"
+            android:fontFamily="sans-serif-condensed"
+            android:shadowRadius="2.0"
+            android:shadowColor="#B0000000" />
     </LinearLayout>
 
 
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 48a06fc..43a856d 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -35,4 +35,6 @@
     <color name="wallpaper_picker_translucent_gray">#66000000</color>
     <color name="folder_items_text_color">#FF333333</color>
     <color name="outline_color">#FFFFFFFF</color>
+    
+    <color name="first_run_cling_circle_background_color">#FF83AEE8</color>
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index e6bb935..9f2a105 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -94,10 +94,11 @@
     </style>
 
     <style name="WorkspaceIcon.AppsCustomize">
-        <item name="android:shadowRadius">0.0</item> <!-- Don't use text shadow -->
         <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:shadowRadius">4.0</item>
+        <item name="android:shadowColor">#FF000000</item>
     </style>
 
     <style name="QSBBar">
diff --git a/src/com/android/launcher3/AppsCustomizePagedView.java b/src/com/android/launcher3/AppsCustomizePagedView.java
index 213e50a..dd870e4 100644
--- a/src/com/android/launcher3/AppsCustomizePagedView.java
+++ b/src/com/android/launcher3/AppsCustomizePagedView.java
@@ -499,13 +499,7 @@
             if (mPressedIcon != null) {
                 mPressedIcon.lockDrawableState();
             }
-
-            // NOTE: We want all transitions from launcher to act as if the wallpaper were enabled
-            // to be consistent.  So re-enable the flag here, and we will re-disable it as necessary
-            // when Launcher resumes and we are still in AllApps.
-            mLauncher.updateWallpaperVisibility(true);
             mLauncher.startActivitySafely(v, appInfo.intent, appInfo);
-
         } else if (v instanceof PagedViewWidget) {
             // Let the user know that they have to long press to add a widget
             if (mWidgetInstructionToast != null) {
diff --git a/src/com/android/launcher3/AppsCustomizeTabHost.java b/src/com/android/launcher3/AppsCustomizeTabHost.java
index f034183..bfcf92a 100644
--- a/src/com/android/launcher3/AppsCustomizeTabHost.java
+++ b/src/com/android/launcher3/AppsCustomizeTabHost.java
@@ -22,6 +22,7 @@
 import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Color;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
@@ -81,13 +82,6 @@
         setOnTabChangedListener(this);
     }
 
-    void selectAppsTab() {
-        setContentTypeImmediate(AppsCustomizePagedView.ContentType.Applications);
-    }
-    void selectWidgetsTab() {
-        setContentTypeImmediate(AppsCustomizePagedView.ContentType.Widgets);
-    }
-
     @Override
     public void setInsets(Rect insets) {
         mInsets.set(insets);
@@ -203,6 +197,9 @@
     }
 
     private void onTabChangedEnd(AppsCustomizePagedView.ContentType type) {
+        int bgAlpha = (int) (255 * (getResources().getInteger(
+            R.integer.config_appsCustomizeSpringLoadedBgAlpha) / 100f));
+        setBackgroundColor(Color.argb(bgAlpha, 0, 0, 0));
         mAppsCustomizePane.setContentType(type);
     }
 
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 30ca737..22492ac 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -949,9 +949,11 @@
         int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
         int heightSize =  MeasureSpec.getSize(heightMeasureSpec);
+        int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
+        int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
         if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
-            int cw = grid.calculateCellWidth(widthSize, mCountX);
-            int ch = grid.calculateCellHeight(heightSize, mCountY);
+            int cw = grid.calculateCellWidth(childWidthSize, mCountX);
+            int ch = grid.calculateCellHeight(childHeightSize, mCountY);
             if (cw != mCellWidth || ch != mCellHeight) {
                 mCellWidth = cw;
                 mCellHeight = ch;
@@ -960,8 +962,8 @@
             }
         }
 
-        int newWidth = widthSize;
-        int newHeight = heightSize;
+        int newWidth = childWidthSize;
+        int newHeight = childHeightSize;
         if (mFixedWidth > 0 && mFixedHeight > 0) {
             newWidth = mFixedWidth;
             newHeight = mFixedHeight;
@@ -973,8 +975,8 @@
         int numHeightGaps = mCountY - 1;
 
         if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
-            int hSpace = widthSize - getPaddingLeft() - getPaddingRight();
-            int vSpace = heightSize - getPaddingTop() - getPaddingBottom();
+            int hSpace = childWidthSize;
+            int vSpace = childHeightSize;
             int hFreeSpace = hSpace - (mCountX * mCellWidth);
             int vFreeSpace = vSpace - (mCountY * mCellHeight);
             mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
@@ -990,15 +992,19 @@
         int maxHeight = 0;
         for (int i = 0; i < count; i++) {
             View child = getChildAt(i);
-            int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth - getPaddingLeft() -
-                    getPaddingRight(), MeasureSpec.EXACTLY);
-            int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight - getPaddingTop() -
-                    getPaddingBottom(), MeasureSpec.EXACTLY);
+            int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth,
+                    MeasureSpec.EXACTLY);
+            int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight,
+                    MeasureSpec.EXACTLY);
             child.measure(childWidthMeasureSpec, childheightMeasureSpec);
             maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
             maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
         }
-        setMeasuredDimension(maxWidth, maxHeight);
+        if (mFixedWidth > 0 && mFixedHeight > 0) {
+            setMeasuredDimension(maxWidth, maxHeight);
+        } else {
+            setMeasuredDimension(widthSize, heightSize);
+        }
     }
 
     @Override
@@ -1006,8 +1012,11 @@
         int count = getChildCount();
         for (int i = 0; i < count; i++) {
             View child = getChildAt(i);
-            child.layout(getPaddingLeft(), getPaddingTop(),
-                    r - l - getPaddingRight(), b - t - getPaddingBottom());
+            int left = getPaddingLeft();
+            int top = getPaddingTop();
+            child.layout(left, top,
+                    left + r - l,
+                    top + b - t);
         }
     }
 
diff --git a/src/com/android/launcher3/Cling.java b/src/com/android/launcher3/Cling.java
index 963702a..7ca6990 100644
--- a/src/com/android/launcher3/Cling.java
+++ b/src/com/android/launcher3/Cling.java
@@ -24,6 +24,7 @@
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
@@ -55,7 +56,7 @@
     private static String FOLDER_LANDSCAPE = "folder_landscape";
     private static String FOLDER_LARGE = "folder_large";
 
-    private static float FIRST_RUN_CIRCLE_BUFFER_DPS = 40;
+    private static float FIRST_RUN_CIRCLE_BUFFER_DPS = 60;
     private static float WORKSPACE_INNER_CIRCLE_RADIUS_DPS = 50;
     private static float WORKSPACE_OUTER_CIRCLE_RADIUS_DPS = 60;
     private static float WORKSPACE_CIRCLE_Y_OFFSET_DPS = 30;
@@ -101,8 +102,10 @@
             mErasePaint.setAlpha(0);
             mErasePaint.setAntiAlias(true);
 
+            int circleColor = getResources().getColor(
+                    R.color.first_run_cling_circle_background_color);
             mBubblePaint = new Paint();
-            mBubblePaint.setColor(0xFFFFFF);
+            mBubblePaint.setColor(circleColor);
             mBubblePaint.setAntiAlias(true);
 
             mDotPaint = new Paint();
diff --git a/src/com/android/launcher3/DynamicGrid.java b/src/com/android/launcher3/DynamicGrid.java
index 664a99c..495e930 100644
--- a/src/com/android/launcher3/DynamicGrid.java
+++ b/src/com/android/launcher3/DynamicGrid.java
@@ -290,8 +290,7 @@
 
     Rect getWorkspacePadding(int orientation) {
         Rect padding = new Rect();
-        if (orientation == CellLayout.LANDSCAPE &&
-                transposeLayoutWithOrientation) {
+        if (isVerticalBarLayout()) {
             // Pad the left and right of the workspace with search/hotseat bar sizes
             padding.set(searchBarSpaceHeightPx, edgeMarginPx,
                     hotseatBarHeightPx, edgeMarginPx);
@@ -321,6 +320,17 @@
         return padding;
     }
 
+    // The rect returned will be extended to below the system ui that covers the workspace
+    Rect getHotseatRect() {
+        if (isVerticalBarLayout()) {
+            return new Rect(availableWidthPx - hotseatBarHeightPx, 0,
+                    Integer.MAX_VALUE, availableHeightPx);
+        } else {
+            return new Rect(0, availableHeightPx - hotseatBarHeightPx,
+                    availableWidthPx, Integer.MAX_VALUE);
+        }
+    }
+
     int calculateCellWidth(int width, int countX) {
         return width / countX;
     }
@@ -338,11 +348,14 @@
         return isLargeTablet;
     }
 
+    boolean isVerticalBarLayout() {
+        return isLandscape && transposeLayoutWithOrientation;
+    }
+
     public void layout(Launcher launcher) {
         FrameLayout.LayoutParams lp;
         Resources res = launcher.getResources();
-        boolean hasVerticalBarLayout = isLandscape &&
-                res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
+        boolean hasVerticalBarLayout = isVerticalBarLayout();
 
         // Layout the search bar space
         View searchBarSpace = launcher.findViewById(R.id.qsb_bar);
@@ -430,7 +443,7 @@
             lp.gravity = Gravity.BOTTOM;
             lp.width = LayoutParams.MATCH_PARENT;
             lp.height = hotseatBarHeightPx;
-            hotseat.setPadding(2 * edgeMarginPx, 0,
+            hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0,
                     2 * edgeMarginPx, 0);
         }
         hotseat.setLayoutParams(lp);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 3bbb39e..8959e7e 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -274,6 +274,7 @@
     private boolean mVisible = false;
     private boolean mAttached = false;
     private static final boolean DISABLE_CLINGS = true;
+    private static final boolean DISABLE_CUSTOM_CLINGS = true;
 
     private static LocaleConfiguration sLocaleConfiguration = null;
 
@@ -895,11 +896,6 @@
 
     @Override
     protected void onPause() {
-        // NOTE: We want all transitions from launcher to act as if the wallpaper were enabled
-        // to be consistent.  So re-enable the flag here, and we will re-disable it as necessary
-        // when Launcher resumes and we are still in AllApps.
-        updateWallpaperVisibility(true);
-
         // Ensure that items added to Launcher are queued until Launcher returns
         InstallShortcutReceiver.enableInstallQueue();
 
@@ -2646,16 +2642,6 @@
         view.setPivotY(view.getHeight() / 2.0f);
     }
 
-    void disableWallpaperIfInAllApps() {
-        // Only disable it if we are in all apps
-        if (isAllAppsVisible()) {
-            if (mAppsCustomizeTabHost != null &&
-                    !mAppsCustomizeTabHost.isTransitioning()) {
-                updateWallpaperVisibility(false);
-            }
-        }
-    }
-
     private void setWorkspaceBackground(boolean workspace) {
         mLauncherView.setBackground(workspace ?
                 mWorkspaceBackgroundDrawable : null);
@@ -2815,7 +2801,6 @@
 
                 @Override
                 public void onAnimationStart(Animator animation) {
-                    updateWallpaperVisibility(true);
                     // Prepare the position
                     toView.setTranslationX(0.0f);
                     toView.setTranslationY(0.0f);
@@ -2827,10 +2812,6 @@
                     dispatchOnLauncherTransitionEnd(fromView, animated, false);
                     dispatchOnLauncherTransitionEnd(toView, animated, false);
 
-                    if (!animationCancelled) {
-                        updateWallpaperVisibility(false);
-                    }
-
                     // Hide the search bar
                     if (mSearchDropTargetBar != null) {
                         mSearchDropTargetBar.hideSearchBar(false);
@@ -2904,7 +2885,6 @@
             dispatchOnLauncherTransitionPrepare(toView, animated, false);
             dispatchOnLauncherTransitionStart(toView, animated, false);
             dispatchOnLauncherTransitionEnd(toView, animated, false);
-            updateWallpaperVisibility(false);
         }
     }
 
@@ -2941,7 +2921,6 @@
         }
 
         setPivotsForZoom(fromView, scaleFactor);
-        updateWallpaperVisibility(true);
         showHotseat(animated);
         if (animated) {
             final LauncherViewPropertyAnimator scaleAnim =
@@ -2973,7 +2952,6 @@
             mStateAnimation.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
-                    updateWallpaperVisibility(true);
                     fromView.setVisibility(View.GONE);
                     dispatchOnLauncherTransitionEnd(fromView, animated, true);
                     dispatchOnLauncherTransitionEnd(toView, animated, true);
@@ -3011,30 +2989,13 @@
         }
     }
 
-    @Override
-    public void onWindowFocusChanged(boolean hasFocus) {
-        if (!hasFocus) {
-            // When another window occludes launcher (like the notification shade, or recents),
-            // ensure that we enable the wallpaper flag so that transitions are done correctly.
-            updateWallpaperVisibility(true);
-        } else {
-            // When launcher has focus again, disable the wallpaper if we are in AllApps
-            mWorkspace.postDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    disableWallpaperIfInAllApps();
-                }
-            }, 500);
-        }
-    }
-
     void showWorkspace(boolean animated) {
         showWorkspace(animated, null);
     }
 
     void showWorkspace(boolean animated, Runnable onCompleteRunnable) {
         if (mState != State.WORKSPACE) {
-            boolean wasInSpringLoadedMode = (mState == State.APPS_CUSTOMIZE_SPRING_LOADED);
+            boolean wasInSpringLoadedMode = (mState != State.WORKSPACE);
             mWorkspace.setVisibility(View.VISIBLE);
             hideAppsCustomizeHelper(State.WORKSPACE, animated, false, onCompleteRunnable);
 
@@ -4166,16 +4127,33 @@
             // If we're not using the default workspace layout, replace workspace cling
             // with a custom workspace cling (usually specified in an overlay)
             // For now, only do this on tablets
-            if (mSharedPrefs.getInt(LauncherProvider.DEFAULT_WORKSPACE_RESOURCE_ID, 0) != 0 &&
-                    getResources().getBoolean(R.bool.config_useCustomClings)) {
-                // Use a custom cling
-                View cling = findViewById(R.id.workspace_cling);
-                ViewGroup clingParent = (ViewGroup) cling.getParent();
-                int clingIndex = clingParent.indexOfChild(cling);
-                clingParent.removeViewAt(clingIndex);
-                View customCling = mInflater.inflate(R.layout.custom_workspace_cling, clingParent, false);
-                clingParent.addView(customCling, clingIndex);
-                customCling.setId(R.id.workspace_cling);
+            if (!DISABLE_CUSTOM_CLINGS) {
+                if (mSharedPrefs.getInt(LauncherProvider.DEFAULT_WORKSPACE_RESOURCE_ID, 0) != 0 &&
+                        getResources().getBoolean(R.bool.config_useCustomClings)) {
+                    // Use a custom cling
+                    View cling = findViewById(R.id.workspace_cling);
+                    ViewGroup clingParent = (ViewGroup) cling.getParent();
+                    int clingIndex = clingParent.indexOfChild(cling);
+                    clingParent.removeViewAt(clingIndex);
+                    View customCling = mInflater.inflate(R.layout.custom_workspace_cling, clingParent, false);
+                    clingParent.addView(customCling, clingIndex);
+                    customCling.setId(R.id.workspace_cling);
+                }
+            }
+            Cling cling = (Cling) findViewById(R.id.first_run_cling);
+            if (cling != null) {
+                String sbHintStr = getFirstRunClingSearchBarHint();
+                String ccHintStr = getFirstRunCustomContentHint();
+                if (!sbHintStr.isEmpty()) {
+                    TextView sbHint = (TextView) cling.findViewById(R.id.search_bar_hint);
+                    sbHint.setText(sbHintStr);
+                    sbHint.setVisibility(View.VISIBLE);
+                }
+                if (!ccHintStr.isEmpty()) {
+                    TextView ccHint = (TextView) cling.findViewById(R.id.custom_content_hint);
+                    ccHint.setText(ccHintStr);
+                    ccHint.setVisibility(View.VISIBLE);
+                }
             }
             initCling(R.id.first_run_cling, null, false, true);
         } else {
@@ -4183,6 +4161,13 @@
         }
     }
 
+    protected String getFirstRunClingSearchBarHint() {
+        return "";
+    }
+    protected String getFirstRunCustomContentHint() {
+        return "";
+    }
+
     public void showFirstRunWorkspaceCling() {
         // Enable the clings only if they have not been dismissed before
         if (isClingsEnabled() &&
diff --git a/src/com/android/launcher3/LauncherBackupAgent.java b/src/com/android/launcher3/LauncherBackupAgent.java
new file mode 100644
index 0000000..bb15ca1
--- /dev/null
+++ b/src/com/android/launcher3/LauncherBackupAgent.java
@@ -0,0 +1,655 @@
+/*
+ * 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 com.google.protobuf.nano.InvalidProtocolBufferNanoException;
+import com.google.protobuf.nano.MessageNano;
+
+import com.android.launcher3.LauncherSettings.ChangeLogColumns;
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.LauncherSettings.WorkspaceScreens;
+import com.android.launcher3.backup.BackupProtos;
+import com.android.launcher3.backup.BackupProtos.CheckedMessage;
+import com.android.launcher3.backup.BackupProtos.Favorite;
+import com.android.launcher3.backup.BackupProtos.Journal;
+import com.android.launcher3.backup.BackupProtos.Key;
+import com.android.launcher3.backup.BackupProtos.Screen;
+
+import android.app.backup.BackupAgent;
+import android.app.backup.BackupDataInput;
+import android.app.backup.BackupDataOutput;
+import android.app.backup.BackupManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.os.ParcelFileDescriptor;
+import android.provider.BaseColumns;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.zip.CRC32;
+
+/**
+ * Persist the launcher home state across calamities.
+ */
+public class LauncherBackupAgent extends BackupAgent {
+
+    private static final String TAG = "LauncherBackupAgent";
+    private static final boolean DEBUG = false;
+
+    private static final int MAX_JOURNAL_SIZE = 1000000;
+
+    private static BackupManager sBackupManager;
+
+    private static final String[] FAVORITE_PROJECTION = {
+            Favorites._ID,                     // 0
+            Favorites.APPWIDGET_ID,            // 1
+            Favorites.APPWIDGET_PROVIDER,      // 2
+            Favorites.CELLX,                   // 3
+            Favorites.CELLY,                   // 4
+            Favorites.CONTAINER,               // 5
+            Favorites.ICON,                    // 6
+            Favorites.ICON_PACKAGE,            // 7
+            Favorites.ICON_RESOURCE,           // 8
+            Favorites.ICON_TYPE,               // 9
+            Favorites.ITEM_TYPE,               // 10
+            Favorites.INTENT,                  // 11
+            Favorites.SCREEN,                  // 12
+            Favorites.SPANX,                   // 13
+            Favorites.SPANY,                   // 14
+            Favorites.TITLE,                   // 15
+    };
+
+    private static final int ID_INDEX = 0;
+    private static final int APPWIDGET_ID_INDEX = 1;
+    private static final int APPWIDGET_PROVIDER_INDEX = 2;
+    private static final int CELLX_INDEX = 3;
+    private static final int CELLY_INDEX = 4;
+    private static final int CONTAINER_INDEX = 5;
+    private static final int ICON_INDEX = 6;
+    private static final int ICON_PACKAGE_INDEX = 7;
+    private static final int ICON_RESOURCE_INDEX = 8;
+    private static final int ICON_TYPE_INDEX = 9;
+    private static final int ITEM_TYPE_INDEX = 10;
+    private static final int INTENT_INDEX = 11;
+    private static final int SCREEN_INDEX = 12;
+    private static final int SPANX_INDEX = 13 ;
+    private static final int SPANY_INDEX = 14;
+    private static final int TITLE_INDEX = 15;
+
+    private static final String[] SCREEN_PROJECTION = {
+            WorkspaceScreens._ID,              // 0
+            WorkspaceScreens.SCREEN_RANK       // 1
+    };
+
+    private static final int SCREEN_RANK_INDEX = 1;
+
+    private static final String[] ID_ONLY_PROJECTION = {
+            BaseColumns._ID
+    };
+
+
+    /**
+     * Notify the backup manager that out database is dirty.
+     *
+     * <P>This does not force an immediate backup.
+     *
+     * @param context application context
+     */
+    public static void dataChanged(Context context) {
+        if (sBackupManager == null) {
+            sBackupManager = new BackupManager(context);
+        }
+        sBackupManager.dataChanged();
+    }
+
+    /**
+     * Back up launcher data so we can restore the user's state on a new device.
+     *
+     * <P>The journal is a timestamp and a list of keys that were saved as of that time.
+     *
+     * <P>Keys may come back in any order, so each key/value is one complete row of the database.
+     *
+     * @param oldState notes from the last backup
+     * @param data incremental key/value pairs to persist off-device
+     * @param newState notes for the next backup
+     * @throws IOException
+     */
+    @Override
+    public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+            ParcelFileDescriptor newState)
+            throws IOException {
+        Log.v(TAG, "onBackup");
+
+        Journal in = readJournal(oldState);
+        Journal out = new Journal();
+
+        long lastBackupTime = in.t;
+        out.t = System.currentTimeMillis();
+        out.rows = 0;
+        out.bytes = 0;
+
+        Log.v(TAG, "lastBackupTime=" + lastBackupTime);
+
+        ArrayList<Key> keys = new ArrayList<Key>();
+        backupFavorites(in, data, out, keys);
+        backupScreens(in, data, out, keys);
+
+        out.key = keys.toArray(BackupProtos.Key.EMPTY_ARRAY);
+        writeJournal(newState, out);
+        Log.v(TAG, "onBackup: wrote " + out.bytes + "b in " + out.rows + " rows.");
+
+        Log.v(TAG, "onBackup: finished");
+    }
+
+    /**
+     * Restore home screen from the restored data stream.
+     *
+     * <P>Keys may arrive in any order.
+     *
+     * @param data the key/value pairs from the server
+     * @param versionCode the version of the app that generated the data
+     * @param newState notes for the next backup
+     * @throws IOException
+     */
+    @Override
+    public void onRestore(BackupDataInput data, int versionCode, ParcelFileDescriptor newState)
+            throws IOException {
+        Log.v(TAG, "onRestore");
+        int numRows = 0;
+        Journal out = new Journal();
+
+        ArrayList<Key> keys = new ArrayList<Key>();
+        byte[] buffer = new byte[512];
+        while (data.readNextHeader()) {
+            numRows++;
+            String backupKey = data.getKey();
+            int dataSize = data.getDataSize();
+            if (buffer.length < dataSize) {
+                buffer = new byte[dataSize];
+            }
+            Key key = null;
+            int bytesRead = data.readEntityData(buffer, 0, dataSize);
+            if (DEBUG) {
+                Log.d(TAG, "read " + bytesRead + " of " + dataSize + " available");
+            }
+            try {
+                key = backupKeyToKey(backupKey);
+                switch (key.type) {
+                    case Key.FAVORITE:
+                        restoreFavorite(key, buffer, dataSize, keys);
+                        break;
+
+                    case Key.SCREEN:
+                        restoreScreen(key, buffer, dataSize, keys);
+                        break;
+
+                    default:
+                        Log.w(TAG, "unknown restore entity type: " + key.type);
+                        break;
+                }
+            } catch (KeyParsingException e) {
+                Log.w(TAG, "ignoring unparsable backup key: " + backupKey);
+            }
+        }
+
+        // clear the output journal time, to force a full backup to
+        // will catch any changes the restore process might have made
+        out.t = 0;
+        out.key = keys.toArray(BackupProtos.Key.EMPTY_ARRAY);
+        writeJournal(newState, out);
+        Log.v(TAG, "onRestore: read " + numRows + " rows");
+    }
+
+    /**
+     * Write all modified favorites to the data stream.
+     *
+     *
+     * @param in notes from last backup
+     * @param data output stream for key/value pairs
+     * @param out notes about this backup
+     * @param keys keys to mark as clean in the notes for next backup
+     * @throws IOException
+     */
+    private void backupFavorites(Journal in, BackupDataOutput data, Journal out,
+            ArrayList<Key> keys)
+            throws IOException {
+        // read the old ID set
+        Set<String> savedIds = new HashSet<String>();
+        for(int i = 0; i < in.key.length; i++) {
+            Key key = in.key[i];
+            if (key.type == Key.FAVORITE) {
+                savedIds.add(keyToBackupKey(key));
+            }
+        }
+        if (DEBUG) Log.d(TAG, "favorite savedIds.size()=" + savedIds.size());
+
+        // persist things that have changed since the last backup
+        ContentResolver cr = getContentResolver();
+        String where = ChangeLogColumns.MODIFIED + " > ?";
+        String[] args = {Long.toString(in.t)};
+        String updateOrder = ChangeLogColumns.MODIFIED;
+        Cursor updated = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
+                where, args, updateOrder);
+        if (DEBUG) Log.d(TAG, "favorite updated.getCount()=" + updated.getCount());
+        try {
+            updated.moveToPosition(-1);
+            while(updated.moveToNext()) {
+                final long id = updated.getLong(ID_INDEX);
+                Key key = getKey(Key.FAVORITE, id);
+                byte[] blob = packFavorite(updated);
+                String backupKey = keyToBackupKey(key);
+                data.writeEntityHeader(backupKey, blob.length);
+                data.writeEntityData(blob, blob.length);
+                out.rows++;
+                out.bytes += blob.length;
+                Log.v(TAG, "saving favorite " + backupKey + ": " + id + "/" + blob.length);
+                if(DEBUG) Log.d(TAG, "wrote " +
+                        Base64.encodeToString(blob, 0, blob.length, Base64.NO_WRAP));
+                // remember that is was a new column, so we don't delete it.
+                savedIds.add(backupKey);
+            }
+        } finally {
+            updated.close();
+        }
+        if (DEBUG) Log.d(TAG, "favorite savedIds.size()=" + savedIds.size());
+
+        // build the current ID set
+        String idOrder = BaseColumns._ID;
+        Cursor idCursor = cr.query(Favorites.CONTENT_URI, ID_ONLY_PROJECTION,
+                null, null, idOrder);
+        Set<String> currentIds = new HashSet<String>(idCursor.getCount());
+        try {
+            idCursor.moveToPosition(-1);
+            while(idCursor.moveToNext()) {
+                Key key = getKey(Key.FAVORITE, idCursor.getLong(ID_INDEX));
+                currentIds.add(keyToBackupKey(key));
+                // save the IDs for next time
+                keys.add(key);
+            }
+        } finally {
+            idCursor.close();
+        }
+        if (DEBUG) Log.d(TAG, "favorite currentIds.size()=" + currentIds.size());
+
+        // these IDs must have been deleted
+        savedIds.removeAll(currentIds);
+        for (String deleted : savedIds) {
+            Log.v(TAG, "dropping favorite " + deleted);
+            data.writeEntityHeader(deleted, -1);
+            out.rows++;
+        }
+    }
+
+    /**
+     * Read a favorite from the stream.
+     *
+     * <P>Keys arrive in any order, so screens and containers may not exist yet.
+     *
+     * @param key identifier for the row
+     * @param buffer the serialized proto from the stream, may be larger than dataSize
+     * @param dataSize the size of the proto from the stream
+     * @param keys keys to mark as clean in the notes for next backup
+     */
+    private void restoreFavorite(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
+        Log.v(TAG, "unpacking favorite " + key.id + " (" + dataSize + " bytes)");
+        if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
+                Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
+
+        try {
+            Favorite favorite =  unpackFavorite(buffer, 0, dataSize);
+            if (DEBUG) Log.d(TAG, "unpacked " + favorite.itemType);
+        } catch (InvalidProtocolBufferNanoException e) {
+            Log.w(TAG, "failed to decode proto", e);
+        }
+    }
+
+    /**
+     * Write all modified screens to the data stream.
+     *
+     *
+     * @param in notes from last backup
+     * @param data output stream for key/value pairs
+     * @param out notes about this backup
+     * @param keys keys to mark as clean in the notes for next backup  @throws IOException
+     */
+    private void backupScreens(Journal in, BackupDataOutput data, Journal out,
+            ArrayList<Key> keys)
+            throws IOException {
+        // read the old ID set
+        Set<String> savedIds = new HashSet<String>();
+        for(int i = 0; i < in.key.length; i++) {
+            Key key = in.key[i];
+            if (key.type == Key.SCREEN) {
+                savedIds.add(keyToBackupKey(key));
+            }
+        }
+        if (DEBUG) Log.d(TAG, "screens savedIds.size()=" + savedIds.size());
+
+        // persist things that have changed since the last backup
+        ContentResolver cr = getContentResolver();
+        String where = ChangeLogColumns.MODIFIED + " > ?";
+        String[] args = {Long.toString(in.t)};
+        String updateOrder = ChangeLogColumns.MODIFIED;
+        Cursor updated = cr.query(WorkspaceScreens.CONTENT_URI, SCREEN_PROJECTION,
+                where, args, updateOrder);
+        updated.moveToPosition(-1);
+        if (DEBUG) Log.d(TAG, "screens updated.getCount()=" + updated.getCount());
+        try {
+            while(updated.moveToNext()) {
+                final long id = updated.getLong(ID_INDEX);
+                Key key = getKey(Key.SCREEN, id);
+                byte[] blob = packScreen(updated);
+                String backupKey = keyToBackupKey(key);
+                data.writeEntityHeader(backupKey, blob.length);
+                data.writeEntityData(blob, blob.length);
+                out.rows++;
+                out.bytes += blob.length;
+                Log.v(TAG, "saving screen " + backupKey + ": " + id + "/" + blob.length);
+                if(DEBUG) Log.d(TAG, "wrote " +
+                        Base64.encodeToString(blob, 0, blob.length, Base64.NO_WRAP));
+                // remember that is was a new column, so we don't delete it.
+                savedIds.add(backupKey);
+            }
+        } finally {
+            updated.close();
+        }
+        if (DEBUG) Log.d(TAG, "screen savedIds.size()=" + savedIds.size());
+
+        // build the current ID set
+        String idOrder = BaseColumns._ID;
+        Cursor idCursor = cr.query(WorkspaceScreens.CONTENT_URI, ID_ONLY_PROJECTION,
+                null, null, idOrder);
+        idCursor.moveToPosition(-1);
+        Set<String> currentIds = new HashSet<String>(idCursor.getCount());
+        try {
+            while(idCursor.moveToNext()) {
+                Key key = getKey(Key.SCREEN, idCursor.getLong(ID_INDEX));
+                currentIds.add(keyToBackupKey(key));
+                // save the IDs for next time
+                keys.add(key);
+            }
+        } finally {
+            idCursor.close();
+        }
+        if (DEBUG) Log.d(TAG, "screen currentIds.size()=" + currentIds.size());
+
+        // these IDs must have been deleted
+        savedIds.removeAll(currentIds);
+        for(String deleted: savedIds) {
+            Log.v(TAG, "dropping screen " + deleted);
+            data.writeEntityHeader(deleted, -1);
+            out.rows++;
+        }
+    }
+
+    /**
+     * Read a screen from the stream.
+     *
+     * <P>Keys arrive in any order, so children of this screen may already exist.
+     *
+     * @param key identifier for the row
+     * @param buffer the serialized proto from the stream, may be larger than dataSize
+     * @param dataSize the size of the proto from the stream
+     * @param keys keys to mark as clean in the notes for next backup
+     */
+    private void restoreScreen(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
+        Log.v(TAG, "unpacking screen " + key.id);
+        if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
+                Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
+        try {
+            Screen screen = unpackScreen(buffer, 0, dataSize);
+            if (DEBUG) Log.d(TAG, "unpacked " + screen.rank);
+        } catch (InvalidProtocolBufferNanoException e) {
+            Log.w(TAG, "failed to decode proto", e);
+        }
+    }
+
+    /** create a new key object.
+     *
+     * <P> Keys contain their own checksum instead of using
+     * the heavy-weight CheckedMessage wrapper.
+     */
+    private Key getKey(int type, long id) {
+        Key key = new Key();
+        key.type = type;
+        key.id = id;
+        key.checksum = checkKey(key);
+        return key;
+    }
+
+    /** keys need to be strings, serialize and encode. */
+    private String keyToBackupKey(Key key) {
+        return Base64.encodeToString(Key.toByteArray(key), Base64.NO_WRAP | Base64.NO_PADDING);
+    }
+
+    /** keys need to be strings, decode and parse. */
+    private Key backupKeyToKey(String backupKey) throws KeyParsingException {
+        try {
+            Key key = Key.parseFrom(Base64.decode(backupKey, Base64.DEFAULT));
+            if (key.checksum != checkKey(key)) {
+                key = null;
+                throw new KeyParsingException("invalid key read from stream" + backupKey);
+            }
+            return key;
+        } catch (InvalidProtocolBufferNanoException e) {
+            throw new KeyParsingException(e);
+        } catch (IllegalArgumentException e) {
+            throw new KeyParsingException(e);
+        }
+    }
+
+    /** Compute the checksum over the important bits of a key. */
+    private long checkKey(Key key) {
+        CRC32 checksum = new CRC32();
+        checksum.update(key.type);
+        checksum.update((int) (key.id & 0xffff));
+        checksum.update((int) ((key.id >> 32) & 0xffff));
+        if (!TextUtils.isEmpty(key.name)) {
+            checksum.update(key.name.getBytes());
+        }
+        return checksum.getValue();
+    }
+
+    /** Serialize a Favorite for persistence, including a checksum wrapper. */
+    private byte[] packFavorite(Cursor c) {
+        Favorite favorite = new Favorite();
+        favorite.id = c.getLong(ID_INDEX);
+        favorite.screen = c.getInt(SCREEN_INDEX);
+        favorite.container = c.getInt(CONTAINER_INDEX);
+        favorite.cellX = c.getInt(CELLX_INDEX);
+        favorite.cellY = c.getInt(CELLY_INDEX);
+        favorite.spanX = c.getInt(SPANX_INDEX);
+        favorite.spanY = c.getInt(SPANY_INDEX);
+        favorite.iconType = c.getInt(ICON_TYPE_INDEX);
+        if (favorite.iconType == Favorites.ICON_TYPE_RESOURCE) {
+            String iconPackage = c.getString(ICON_PACKAGE_INDEX);
+            if (!TextUtils.isEmpty(iconPackage)) {
+                favorite.iconPackage = iconPackage;
+            }
+            String iconResource = c.getString(ICON_RESOURCE_INDEX);
+            if (!TextUtils.isEmpty(iconResource)) {
+                favorite.iconResource = iconResource;
+            }
+        }
+        if (favorite.iconType == Favorites.ICON_TYPE_BITMAP) {
+            byte[] blob = c.getBlob(ICON_INDEX);
+            if (blob != null && blob.length > 0) {
+                favorite.icon = blob;
+            }
+        }
+        String title = c.getString(TITLE_INDEX);
+        if (!TextUtils.isEmpty(title)) {
+            favorite.title = title;
+        }
+        String intent = c.getString(INTENT_INDEX);
+        if (!TextUtils.isEmpty(intent)) {
+            favorite.intent = intent;
+        }
+        favorite.itemType = c.getInt(ITEM_TYPE_INDEX);
+        if (favorite.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
+            favorite.appWidgetId = c.getInt(APPWIDGET_ID_INDEX);
+            String appWidgetProvider = c.getString(APPWIDGET_PROVIDER_INDEX);
+            if (!TextUtils.isEmpty(appWidgetProvider)) {
+                favorite.appWidgetProvider = appWidgetProvider;
+            }
+        }
+
+        return writeCheckedBytes(favorite);
+    }
+
+    /** Deserialize a Favorite from persistence, after verifying checksum wrapper. */
+    private Favorite unpackFavorite(byte[] buffer, int offset, int dataSize)
+            throws InvalidProtocolBufferNanoException {
+        Favorite favorite = new Favorite();
+        MessageNano.mergeFrom(favorite, readCheckedBytes(buffer, offset, dataSize));
+        return favorite;
+    }
+
+    /** Serialize a Screen for persistence, including a checksum wrapper. */
+    private byte[] packScreen(Cursor c) {
+        Screen screen = new Screen();
+        screen.id = c.getLong(ID_INDEX);
+        screen.rank = c.getInt(SCREEN_RANK_INDEX);
+
+        return writeCheckedBytes(screen);
+    }
+
+    /** Deserialize a Screen from persistence, after verifying checksum wrapper. */
+    private Screen unpackScreen(byte[] buffer, int offset, int dataSize)
+            throws InvalidProtocolBufferNanoException {
+        Screen screen = new Screen();
+        MessageNano.mergeFrom(screen, readCheckedBytes(buffer, offset, dataSize));
+        return screen;
+    }
+
+    /**
+     * Read the old journal from the input file.
+     *
+     * In the event of any error, just pretend we didn't have a journal,
+     * in that case, do a full backup.
+     *
+     * @param oldState the read-0only file descriptor pointing to the old journal
+     * @return a Journal protocol bugffer
+     */
+    private Journal readJournal(ParcelFileDescriptor oldState) {
+        int fileSize = (int) oldState.getStatSize();
+        int remaining = fileSize;
+        byte[] buffer = null;
+        Journal journal = new Journal();
+        if (remaining < MAX_JOURNAL_SIZE) {
+            FileInputStream inStream = new FileInputStream(oldState.getFileDescriptor());
+            int offset = 0;
+
+            buffer = new byte[remaining];
+            while (remaining > 0) {
+                int bytesRead = 0;
+                try {
+                    bytesRead = inStream.read(buffer, offset, remaining);
+                } catch (IOException e) {
+                    Log.w(TAG, "failed to read the journal", e);
+                    buffer = null;
+                    remaining = 0;
+                }
+                if (bytesRead > 0) {
+                    remaining -= bytesRead;
+                } else {
+                    // act like there is not journal
+                    Log.w(TAG, "failed to read the journal");
+                    buffer = null;
+                    remaining = 0;
+                }
+            }
+
+            if (buffer != null) {
+                try {
+                    MessageNano.mergeFrom(journal, readCheckedBytes(buffer, 0, fileSize));
+                } catch (InvalidProtocolBufferNanoException e) {
+                    Log.d(TAG, "failed to read the journal", e);
+                    journal.clear();
+                }
+            }
+
+            try {
+                inStream.close();
+            } catch (IOException e) {
+                Log.d(TAG, "failed to close the journal", e);
+            }
+        }
+        return journal;
+    }
+
+    /**
+     * Write the new journal to the output file.
+     *
+     * In the event of any error, just pretend we didn't have a journal,
+     * in that case, do a full backup.
+
+     * @param newState the write-only file descriptor pointing to the new journal
+     * @param journal a Journal protocol buffer
+     */
+    private void writeJournal(ParcelFileDescriptor newState, Journal journal) {
+        FileOutputStream outStream = null;
+        try {
+            outStream = new FileOutputStream(newState.getFileDescriptor());
+            outStream.write(writeCheckedBytes(journal));
+            outStream.close();
+        } catch (IOException e) {
+            Log.d(TAG, "failed to write backup journal", e);
+        }
+    }
+
+    /** Wrap a proto in a CheckedMessage and compute the checksum. */
+    private byte[] writeCheckedBytes(MessageNano proto) {
+        CheckedMessage wrapper = new CheckedMessage();
+        wrapper.payload = MessageNano.toByteArray(proto);
+        CRC32 checksum = new CRC32();
+        checksum.update(wrapper.payload);
+        wrapper.checksum = checksum.getValue();
+        return MessageNano.toByteArray(wrapper);
+    }
+
+    /** Unwrap a proto message from a CheckedMessage, verifying the checksum. */
+    private byte[] readCheckedBytes(byte[] buffer, int offset, int dataSize)
+            throws InvalidProtocolBufferNanoException {
+        CheckedMessage wrapper = new CheckedMessage();
+        MessageNano.mergeFrom(wrapper, buffer, offset, dataSize);
+        CRC32 checksum = new CRC32();
+        checksum.update(wrapper.payload);
+        if (wrapper.checksum != checksum.getValue()) {
+            throw new InvalidProtocolBufferNanoException("checksum does not match");
+        }
+        return wrapper.payload;
+    }
+
+    private class KeyParsingException extends Throwable {
+        private KeyParsingException(Throwable cause) {
+            super(cause);
+        }
+
+        public KeyParsingException(String reason) {
+            super(reason);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index fa61b11..471d2d2 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -49,6 +49,7 @@
 import android.util.Log;
 import android.util.Xml;
 
+import com.android.launcher3.LauncherSettings.BaseLauncherColumns;
 import com.android.launcher3.LauncherSettings.Favorites;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -65,7 +66,7 @@
 
     private static final String DATABASE_NAME = "launcher.db";
 
-    private static final int DATABASE_VERSION = 14;
+    private static final int DATABASE_VERSION = 15;
 
     static final String OLD_AUTHORITY = "com.android.launcher2.settings";
     static final String AUTHORITY = "com.android.launcher3.settings";
@@ -146,6 +147,7 @@
         SqlArguments args = new SqlArguments(uri);
 
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        addModifiedTime(initialValues);
         final long rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues);
         if (rowId <= 0) return null;
 
@@ -164,6 +166,7 @@
         try {
             int numValues = values.length;
             for (int i = 0; i < numValues; i++) {
+                addModifiedTime(values[i]);
                 if (dbInsertAndCheck(mOpenHelper, db, args.table, null, values[i]) < 0) {
                     return 0;
                 }
@@ -192,6 +195,7 @@
     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
         SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
 
+        addModifiedTime(values);
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
         int count = db.update(args.table, values, args.where, args.args);
         if (count > 0) sendNotify(uri);
@@ -204,6 +208,13 @@
         if (notify == null || "true".equals(notify)) {
             getContext().getContentResolver().notifyChange(uri, null);
         }
+
+        // always notify the backup agent
+        LauncherBackupAgent.dataChanged(getContext());
+    }
+
+    private void addModifiedTime(ContentValues values) {
+        values.put(LauncherSettings.ChangeLogColumns.MODIFIED, System.currentTimeMillis());
     }
 
     public long generateNewItemId() {
@@ -343,7 +354,8 @@
                     "icon BLOB," +
                     "uri TEXT," +
                     "displayMode INTEGER," +
-                    "appWidgetProvider TEXT" +
+                    "appWidgetProvider TEXT," +
+                    "modified INTEGER NOT NULL DEFAULT 0" +
                     ");");
             addWorkspacesTable(db);
 
@@ -384,7 +396,8 @@
         private void addWorkspacesTable(SQLiteDatabase db) {
             db.execSQL("CREATE TABLE " + TABLE_WORKSPACE_SCREENS + " (" +
                     LauncherSettings.WorkspaceScreens._ID + " INTEGER," +
-                    LauncherSettings.WorkspaceScreens.SCREEN_RANK + " INTEGER" +
+                    LauncherSettings.WorkspaceScreens.SCREEN_RANK + " INTEGER," +
+                    LauncherSettings.ChangeLogColumns.MODIFIED + " INTEGER NOT NULL DEFAULT 0" +
                     ");");
         }
 
@@ -643,6 +656,25 @@
                 }
             }
 
+
+            if (version < 15) {
+                db.beginTransaction();
+                try {
+                    // Insert new column for holding update timestamp
+                    db.execSQL("ALTER TABLE favorites " +
+                            "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;");
+                    db.execSQL("ALTER TABLE workspaceScreens " +
+                            "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;");
+                    db.setTransactionSuccessful();
+                    version = 15;
+                } catch (SQLException ex) {
+                    // Old version remains, which means we wipe old data
+                    Log.e(TAG, ex.getMessage(), ex);
+                } finally {
+                    db.endTransaction();
+                }
+            }
+
             if (version != DATABASE_VERSION) {
                 Log.w(TAG, "Destroying all old data.");
                 db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index ada09e9..988e5ef 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -23,7 +23,16 @@
  * Settings related utilities.
  */
 class LauncherSettings {
-    static interface BaseLauncherColumns extends BaseColumns {
+    /** Columns required on table staht will be subject to backup and restore. */
+    static interface ChangeLogColumns extends BaseColumns {
+        /**
+         * The time of the last update to this row.
+         * <P>Type: INTEGER</P>
+         */
+        static final String MODIFIED = "modified";
+    }
+
+    static interface BaseLauncherColumns extends ChangeLogColumns {
         /**
          * Descriptive name of the gesture that can be displayed to the user.
          * <P>Type: TEXT</P>
@@ -95,7 +104,7 @@
      *
      * Tracks the order of workspace screens.
      */
-    static final class WorkspaceScreens implements BaseColumns {
+    static final class WorkspaceScreens implements ChangeLogColumns {
         /**
          * The content:// style URL for this table
          */
diff --git a/src/com/android/launcher3/LiveWallpaperListAdapter.java b/src/com/android/launcher3/LiveWallpaperListAdapter.java
index a6facaa..9d0f48b 100644
--- a/src/com/android/launcher3/LiveWallpaperListAdapter.java
+++ b/src/com/android/launcher3/LiveWallpaperListAdapter.java
@@ -17,12 +17,10 @@
 package com.android.launcher3;
 
 import android.app.WallpaperInfo;
+import android.app.WallpaperManager;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
@@ -52,7 +50,7 @@
     private final LayoutInflater mInflater;
     private final PackageManager mPackageManager;
 
-    private List<LiveWallpaperInfo> mWallpapers;
+    private List<LiveWallpaperTile> mWallpapers;
 
     @SuppressWarnings("unchecked")
     public LiveWallpaperListAdapter(Context context) {
@@ -63,20 +61,11 @@
                 new Intent(WallpaperService.SERVICE_INTERFACE),
                 PackageManager.GET_META_DATA);
 
-        mWallpapers = generatePlaceholderViews(list.size());
+        mWallpapers = new ArrayList<LiveWallpaperTile>();
 
         new LiveWallpaperEnumerator(context).execute(list);
     }
 
-    private List<LiveWallpaperInfo> generatePlaceholderViews(int amount) {
-        ArrayList<LiveWallpaperInfo> list = new ArrayList<LiveWallpaperInfo>(amount);
-        for (int i = 0; i < amount; i++) {
-            LiveWallpaperInfo info = new LiveWallpaperInfo();
-            list.add(info);
-        }
-        return list;
-    }
-
     public int getCount() {
         if (mWallpapers == null) {
             return 0;
@@ -84,7 +73,7 @@
         return mWallpapers.size();
     }
 
-    public Object getItem(int position) {
+    public LiveWallpaperTile getItem(int position) {
         return mWallpapers.get(position);
     }
 
@@ -103,31 +92,42 @@
 
         WallpaperPickerActivity.setWallpaperItemPaddingToZero((FrameLayout) view);
 
-        LiveWallpaperInfo wallpaperInfo = mWallpapers.get(position);
+        LiveWallpaperTile wallpaperInfo = mWallpapers.get(position);
         ImageView image = (ImageView) view.findViewById(R.id.wallpaper_image);
         ImageView icon = (ImageView) view.findViewById(R.id.wallpaper_icon);
-        if (wallpaperInfo.thumbnail != null) {
-            image.setImageDrawable(wallpaperInfo.thumbnail);
+        if (wallpaperInfo.mThumbnail != null) {
+            image.setImageDrawable(wallpaperInfo.mThumbnail);
             icon.setVisibility(View.GONE);
         } else {
-            icon.setImageDrawable(wallpaperInfo.info.loadIcon(mPackageManager));
+            icon.setImageDrawable(wallpaperInfo.mInfo.loadIcon(mPackageManager));
             icon.setVisibility(View.VISIBLE);
         }
 
         TextView label = (TextView) view.findViewById(R.id.wallpaper_item_label);
-        label.setText(wallpaperInfo.info.loadLabel(mPackageManager));
+        label.setText(wallpaperInfo.mInfo.loadLabel(mPackageManager));
 
         return view;
     }
 
-    public class LiveWallpaperInfo {
-        public Drawable thumbnail;
-        public WallpaperInfo info;
-        public Intent intent;
+    public static class LiveWallpaperTile extends WallpaperPickerActivity.WallpaperTileInfo {
+        private Drawable mThumbnail;
+        private WallpaperInfo mInfo;
+        public LiveWallpaperTile(Drawable thumbnail, WallpaperInfo info, Intent intent) {
+            mThumbnail = thumbnail;
+            mInfo = info;
+        }
+        public void onClick(WallpaperPickerActivity a) {
+            Intent preview = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);
+            preview.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT,
+                    mInfo.getComponent());
+            a.onLiveWallpaperPickerLaunch();
+            Utilities.startActivityForResultSafely(
+                    a, preview, WallpaperPickerActivity.PICK_LIVE_WALLPAPER);
+        }
     }
 
     private class LiveWallpaperEnumerator extends
-            AsyncTask<List<ResolveInfo>, LiveWallpaperInfo, Void> {
+            AsyncTask<List<ResolveInfo>, LiveWallpaperTile, Void> {
         private Context mContext;
         private int mWallpaperPosition;
 
@@ -168,12 +168,12 @@
                     continue;
                 }
 
-                LiveWallpaperInfo wallpaper = new LiveWallpaperInfo();
-                wallpaper.intent = new Intent(WallpaperService.SERVICE_INTERFACE);
-                wallpaper.intent.setClassName(info.getPackageName(), info.getServiceName());
-                wallpaper.info = info;
 
                 Drawable thumb = info.loadThumbnail(packageManager);
+                Intent launchIntent = new Intent(WallpaperService.SERVICE_INTERFACE);
+                launchIntent.setClassName(info.getPackageName(), info.getServiceName());
+                LiveWallpaperTile wallpaper = new LiveWallpaperTile(thumb, info, launchIntent);
+
                 // TODO: generate a default thumb
                 /*
                 final Resources res = mContext.getResources();
@@ -211,7 +211,6 @@
 
                     thumb = new BitmapDrawable(res, thumbnail);
                 }*/
-                wallpaper.thumbnail = thumb;
                 publishProgress(wallpaper);
             }
 
@@ -219,9 +218,9 @@
         }
 
         @Override
-        protected void onProgressUpdate(LiveWallpaperInfo...infos) {
-            for (LiveWallpaperInfo info : infos) {
-                info.thumbnail.setDither(true);
+        protected void onProgressUpdate(LiveWallpaperTile...infos) {
+            for (LiveWallpaperTile info : infos) {
+                info.mThumbnail.setDither(true);
                 if (mWallpaperPosition < mWallpapers.size()) {
                     mWallpapers.set(mWallpaperPosition, info);
                 } else {
diff --git a/src/com/android/launcher3/PagedViewIcon.java b/src/com/android/launcher3/PagedViewIcon.java
index fa9ec5a..c6d5e49 100644
--- a/src/com/android/launcher3/PagedViewIcon.java
+++ b/src/com/android/launcher3/PagedViewIcon.java
@@ -18,6 +18,9 @@
 
 import android.content.Context;
 import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Region;
+import android.graphics.Region.Op;
 import android.util.AttributeSet;
 import android.util.TypedValue;
 import android.widget.TextView;
@@ -99,4 +102,27 @@
             setAlpha(1f);
         }
     }
+
+    @Override
+    public void draw(Canvas canvas) {
+        // If text is transparent, don't draw any shadow
+        if (getCurrentTextColor() == getResources().getColor(android.R.color.transparent)) {
+            getPaint().clearShadowLayer();
+            super.draw(canvas);
+            return;
+        }
+
+        // We enhance the shadow by drawing the shadow twice
+        getPaint().setShadowLayer(BubbleTextView.SHADOW_LARGE_RADIUS, 0.0f,
+                BubbleTextView.SHADOW_Y_OFFSET, BubbleTextView.SHADOW_LARGE_COLOUR);
+        super.draw(canvas);
+        canvas.save(Canvas.CLIP_SAVE_FLAG);
+        canvas.clipRect(getScrollX(), getScrollY() + getExtendedPaddingTop(),
+                getScrollX() + getWidth(),
+                getScrollY() + getHeight(), Region.Op.INTERSECT);
+        getPaint().setShadowLayer(BubbleTextView.SHADOW_SMALL_RADIUS, 0.0f, 0.0f,
+                BubbleTextView.SHADOW_SMALL_COLOUR);
+        super.draw(canvas);
+        canvas.restore();
+    }
 }
diff --git a/src/com/android/launcher3/SavedWallpaperImages.java b/src/com/android/launcher3/SavedWallpaperImages.java
index 9766a8a..f00f62f 100644
--- a/src/com/android/launcher3/SavedWallpaperImages.java
+++ b/src/com/android/launcher3/SavedWallpaperImages.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3;
 
+import android.app.Activity;
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
@@ -27,6 +28,13 @@
 import android.graphics.drawable.Drawable;
 import android.util.Log;
 import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ListAdapter;
+
+import com.android.photos.BitmapRegionTileSource;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -35,21 +43,49 @@
 import java.util.ArrayList;
 
 
-public class SavedWallpaperImages {
+public class SavedWallpaperImages extends BaseAdapter implements ListAdapter {
     private static String TAG = "Launcher3.SavedWallpaperImages";
     private ImageDb mDb;
-    ArrayList<Integer> mIds;
-    ArrayList<Drawable> mThumbs;
+    ArrayList<SavedWallpaperTile> mImages;
     Context mContext;
+    LayoutInflater mLayoutInflater;
 
-    public SavedWallpaperImages(Context context) {
+    public static class SavedWallpaperTile extends WallpaperPickerActivity.WallpaperTileInfo {
+        private int mDbId;
+        private Drawable mThumb;
+        public SavedWallpaperTile(int dbId, Drawable thumb) {
+            mDbId = dbId;
+            mThumb = thumb;
+        }
+        public void onClick(WallpaperPickerActivity a) {
+            String imageFilename = a.getSavedImages().getImageFilename(mDbId);
+            File file = new File(a.getFilesDir(), imageFilename);
+            CropView v = a.getCropView();
+            v.setTileSource(new BitmapRegionTileSource(a, file.getAbsolutePath(), 1024, 0), null);
+            v.moveToLeft();
+            v.setTouchEnabled(false);
+        }
+        public void onSave(WallpaperPickerActivity a) {
+            boolean finishActivityWhenDone = true;
+            String imageFilename = a.getSavedImages().getImageFilename(mDbId);
+            a.setWallpaper(imageFilename, finishActivityWhenDone);
+        }
+        public void onDelete(WallpaperPickerActivity a) {
+            a.getSavedImages().deleteImage(mDbId);
+        }
+        public boolean isSelectable() {
+            return true;
+        }
+    }
+
+    public SavedWallpaperImages(Activity context) {
         mDb = new ImageDb(context);
         mContext = context;
+        mLayoutInflater = context.getLayoutInflater();
     }
 
     public void loadThumbnailsAndImageIdList() {
-        mIds = new ArrayList<Integer>();
-        mThumbs = new ArrayList<Drawable>();
+        mImages = new ArrayList<SavedWallpaperTile>();
         SQLiteDatabase db = mDb.getReadableDatabase();
         Cursor result = db.query(ImageDb.TABLE_NAME,
                 new String[] { ImageDb.COLUMN_ID,
@@ -66,19 +102,31 @@
             File file = new File(mContext.getFilesDir(), filename);
             Bitmap thumb = BitmapFactory.decodeFile(file.getAbsolutePath());
             if (thumb != null) {
-                mIds.add(result.getInt(0));
-                mThumbs.add(new BitmapDrawable(thumb));
+                mImages.add(new SavedWallpaperTile(result.getInt(0), new BitmapDrawable(thumb)));
             }
         }
         result.close();
     }
 
-    public ArrayList<Drawable> getThumbnails() {
-        return mThumbs;
+    public int getCount() {
+        return mImages.size();
     }
 
-    public ArrayList<Integer> getImageIds() {
-        return mIds;
+    public SavedWallpaperTile getItem(int position) {
+        return mImages.get(position);
+    }
+
+    public long getItemId(int position) {
+        return position;
+    }
+
+    public View getView(int position, View convertView, ViewGroup parent) {
+        Drawable thumbDrawable = mImages.get(position).mThumb;
+        if (thumbDrawable == null) {
+            Log.e(TAG, "Error decoding thumbnail for wallpaper #" + position);
+        }
+        return WallpaperPickerActivity.createImageTileView(
+                mLayoutInflater, position, convertView, parent, thumbDrawable);
     }
 
     public String getImageFilename(int id) {
diff --git a/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java b/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java
index 9fd0d0a..ab2f5d7 100644
--- a/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java
+++ b/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java
@@ -41,6 +41,21 @@
 
     private List<ResolveInfo> mThirdPartyWallpaperPickers = new ArrayList<ResolveInfo>();
 
+    public static class ThirdPartyWallpaperTile extends WallpaperPickerActivity.WallpaperTileInfo {
+        private ResolveInfo mResolveInfo;
+        public ThirdPartyWallpaperTile(ResolveInfo resolveInfo) {
+            mResolveInfo = resolveInfo;
+        }
+        public void onClick(WallpaperPickerActivity a) {
+            final ComponentName itemComponentName = new ComponentName(
+                    mResolveInfo.activityInfo.packageName, mResolveInfo.activityInfo.name);
+            Intent launchIntent = new Intent(Intent.ACTION_SET_WALLPAPER);
+            launchIntent.setComponent(itemComponentName);
+            Utilities.startActivityForResultSafely(
+                    a, launchIntent, WallpaperPickerActivity.PICK_WALLPAPER_THIRD_PARTY_ACTIVITY);
+        }
+    }
+
     public ThirdPartyWallpaperPickerListAdapter(Context context) {
         mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
         mPackageManager = context.getPackageManager();
@@ -88,7 +103,7 @@
         return mThirdPartyWallpaperPickers.size();
     }
 
-    public Object getItem(int position) {
+    public ResolveInfo getItem(int position) {
         return mThirdPartyWallpaperPickers.get(position);
     }
 
diff --git a/src/com/android/launcher3/WallpaperCropActivity.java b/src/com/android/launcher3/WallpaperCropActivity.java
index f359480..bc8df6c 100644
--- a/src/com/android/launcher3/WallpaperCropActivity.java
+++ b/src/com/android/launcher3/WallpaperCropActivity.java
@@ -162,7 +162,6 @@
     }
 
     protected void setWallpaper(String filePath, final boolean finishActivityWhenDone) {
-
         BitmapCropTask cropTask = new BitmapCropTask(this,
                 filePath, null, 0, 0, true, false, null);
         final Point bounds = cropTask.getImageBounds();
@@ -200,7 +199,7 @@
                 }
             }
         };
-        BitmapCropTask cropTask = new BitmapCropTask(res, resId,
+        BitmapCropTask cropTask = new BitmapCropTask(this, res, resId,
                 crop, outSize.x, outSize.y,
                 true, false, onEndCrop);
         cropTask.execute();
@@ -287,7 +286,7 @@
                 }
             }
         };
-        BitmapCropTask cropTask = new BitmapCropTask(uri,
+        BitmapCropTask cropTask = new BitmapCropTask(this, uri,
                 cropRect, outWidth, outHeight, true, false, onEndCrop);
         if (onBitmapCroppedHandler != null) {
             cropTask.setOnBitmapCropped(onBitmapCroppedHandler);
@@ -299,7 +298,7 @@
         public void onBitmapCropped(byte[] imageBytes);
     }
 
-    protected class BitmapCropTask extends AsyncTask<Void, Void, Boolean> {
+    protected static class BitmapCropTask extends AsyncTask<Void, Void, Boolean> {
         Uri mInUri = null;
         Context mContext;
         String mInFilePath;
@@ -309,7 +308,6 @@
         RectF mCropBounds = null;
         int mOutWidth, mOutHeight;
         int mRotation = 0; // for now
-        protected final WallpaperManager mWPManager;
         String mOutputFormat = "jpg"; // for now
         boolean mSetWallpaper;
         boolean mSaveCroppedBitmap;
@@ -324,7 +322,6 @@
                 boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
             mContext = c;
             mInFilePath = filePath;
-            mWPManager = WallpaperManager.getInstance(getApplicationContext());
             init(cropBounds, outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
         }
 
@@ -332,24 +329,23 @@
                 RectF cropBounds, int outWidth, int outHeight,
                 boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
             mInImageBytes = imageBytes;
-            mWPManager = WallpaperManager.getInstance(getApplicationContext());
             init(cropBounds, outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
         }
 
-        public BitmapCropTask(Uri inUri,
+        public BitmapCropTask(Context c, Uri inUri,
                 RectF cropBounds, int outWidth, int outHeight,
                 boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
+            mContext = c;
             mInUri = inUri;
-            mWPManager = WallpaperManager.getInstance(getApplicationContext());
             init(cropBounds, outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
         }
 
-        public BitmapCropTask(Resources res, int inResId,
+        public BitmapCropTask(Context c, Resources res, int inResId,
                 RectF cropBounds, int outWidth, int outHeight,
                 boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
+            mContext = c;
             mInResId = inResId;
             mResources = res;
-            mWPManager = WallpaperManager.getInstance(getApplicationContext());
             init(cropBounds, outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
         }
 
@@ -385,7 +381,7 @@
                 try {
                     if (mInUri != null) {
                         mInStream = new BufferedInputStream(
-                                getContentResolver().openInputStream(mInUri));
+                                mContext.getContentResolver().openInputStream(mInUri));
                     } else if (mInFilePath != null) {
                         mInStream = mContext.openFileInput(mInFilePath);
                     } else if (mInImageBytes != null) {
@@ -426,16 +422,17 @@
 
             regenerateInputStream();
 
-            if (mNoCrop && mInStream != null) {
+            WallpaperManager wallpaperManager = null;
+            if (mSetWallpaper) {
+                wallpaperManager = WallpaperManager.getInstance(mContext.getApplicationContext());
+            }
+            if (mSetWallpaper && mNoCrop && mInStream != null) {
                 try {
-                    mWPManager.setStream(mInStream);
+                    wallpaperManager.setStream(mInStream);
                 } catch (IOException e) {
                     Log.w(LOGTAG, "cannot write stream to wallpaper", e);
                     failure = true;
                 }
-                if (mOnEndRunnable != null) {
-                    mOnEndRunnable.run();
-                }
                 return !failure;
             }
             if (mInStream != null) {
@@ -534,26 +531,18 @@
                 ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048);
                 if (crop.compress(cf, DEFAULT_COMPRESS_QUALITY, tmpOut)) {
                     // If we need to set to the wallpaper, set it
-                    if (mSetWallpaper && mWPManager != null) {
-                        if (mWPManager == null) {
-                            Log.w(LOGTAG, "no wallpaper manager");
-                            failure = true;
-                        } else {
-                            try {
-                                byte[] outByteArray = tmpOut.toByteArray();
-                                mWPManager.setStream(new ByteArrayInputStream(outByteArray));
-                                if (mOnBitmapCroppedHandler != null) {
-                                    mOnBitmapCroppedHandler.onBitmapCropped(outByteArray);
-                                }
-                            } catch (IOException e) {
-                                Log.w(LOGTAG, "cannot write stream to wallpaper", e);
-                                failure = true;
+                    if (mSetWallpaper && wallpaperManager != null) {
+                        try {
+                            byte[] outByteArray = tmpOut.toByteArray();
+                            wallpaperManager.setStream(new ByteArrayInputStream(outByteArray));
+                            if (mOnBitmapCroppedHandler != null) {
+                                mOnBitmapCroppedHandler.onBitmapCropped(outByteArray);
                             }
+                        } catch (IOException e) {
+                            Log.w(LOGTAG, "cannot write stream to wallpaper", e);
+                            failure = true;
                         }
                     }
-                    if (mOnEndRunnable != null) {
-                        mOnEndRunnable.run();
-                    }
                 } else {
                     Log.w(LOGTAG, "cannot compress bitmap");
                     failure = true;
@@ -569,8 +558,9 @@
 
         @Override
         protected void onPostExecute(Boolean result) {
-            setResult(Activity.RESULT_OK);
-            finish();
+            if (mOnEndRunnable != null) {
+                mOnEndRunnable.run();
+            }
         }
     }
 
diff --git a/src/com/android/launcher3/WallpaperPickerActivity.java b/src/com/android/launcher3/WallpaperPickerActivity.java
index 868b1df..5f35cde 100644
--- a/src/com/android/launcher3/WallpaperPickerActivity.java
+++ b/src/com/android/launcher3/WallpaperPickerActivity.java
@@ -21,12 +21,10 @@
 import android.app.Activity;
 import android.app.WallpaperInfo;
 import android.app.WallpaperManager;
-import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.database.DataSetObserver;
@@ -35,6 +33,7 @@
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.LevelListDrawable;
 import android.net.Uri;
@@ -47,7 +46,6 @@
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
-import android.view.SubMenu;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
@@ -57,26 +55,20 @@
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.ListAdapter;
-import android.widget.SpinnerAdapter;
 
 import com.android.photos.BitmapRegionTileSource;
 
-import java.io.File;
+import java.io.IOException;
 import java.util.ArrayList;
-import java.util.List;
 
 public class WallpaperPickerActivity extends WallpaperCropActivity {
     static final String TAG = "Launcher.WallpaperPickerActivity";
 
-    private static final int IMAGE_PICK = 5;
-    private static final int PICK_WALLPAPER_THIRD_PARTY_ACTIVITY = 6;
-    private static final int PICK_LIVE_WALLPAPER = 7;
+    public static final int IMAGE_PICK = 5;
+    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 ArrayList<Drawable> mBundledWallpaperThumbs;
-    private ArrayList<Integer> mBundledWallpaperResIds;
-    private Resources mWallpaperResources;
-
     private View mSelectedThumb;
     private boolean mIgnoreNextTap;
     private OnClickListener mThumbnailOnClickListener;
@@ -92,13 +84,80 @@
     private SavedWallpaperImages mSavedImages;
     private WallpaperInfo mLiveWallpaperInfoOnPickerLaunch;
 
-    private static class ThumbnailMetaData {
-        public TileType mTileType;
-        public Uri mWallpaperUri;
-        public int mSavedWallpaperDbId;
-        public int mWallpaperResId;
-        public LiveWallpaperListAdapter.LiveWallpaperInfo mLiveWallpaperInfo;
-        public ResolveInfo mThirdPartyWallpaperPickerInfo;
+    public static abstract class WallpaperTileInfo {
+        public void onClick(WallpaperPickerActivity a) {}
+        public void onSave(WallpaperPickerActivity a) {}
+        public void onDelete() {}
+        public boolean isSelectable() { return false; }
+    }
+
+    public static class PickImageInfo extends WallpaperTileInfo {
+        public void onClick(WallpaperPickerActivity a) {
+            Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+            intent.setType("image/*");
+            Utilities.startActivityForResultSafely(a, intent, IMAGE_PICK);
+        }
+    }
+
+    public static class UriWallpaperInfo extends WallpaperTileInfo {
+        private Uri mUri;
+        public UriWallpaperInfo(Uri uri) {
+            mUri = uri;
+        }
+        public void onClick(WallpaperPickerActivity a) {
+            CropView v = a.getCropView();
+            v.setTileSource(new BitmapRegionTileSource(
+                    a, mUri, 1024, 0), null);
+            v.setTouchEnabled(true);
+        }
+
+        public void onSave(final WallpaperPickerActivity a) {
+            boolean finishActivityWhenDone = true;
+            OnBitmapCroppedHandler h = new OnBitmapCroppedHandler() {
+                public void onBitmapCropped(byte[] imageBytes) {
+                    Point thumbSize = getDefaultThumbnailSize(a.getResources());
+                    Bitmap thumb =
+                            createThumbnail(thumbSize, null, null, imageBytes, null, 0, true);
+                    a.getSavedImages().writeImage(thumb, imageBytes);
+                }
+            };
+            a.cropImageAndSetWallpaper(mUri, h, finishActivityWhenDone);
+        }
+        public boolean isSelectable() {
+            return true;
+        }
+    }
+
+    public static class ResourceWallpaperInfo extends WallpaperTileInfo {
+        private Resources mResources;
+        private int mResId;
+        private Drawable mThumb;
+
+        public ResourceWallpaperInfo(Resources res, int resId, Drawable thumb) {
+            mResources = res;
+            mResId = resId;
+            mThumb = thumb;
+        }
+        public void onClick(WallpaperPickerActivity a) {
+            BitmapRegionTileSource source = new BitmapRegionTileSource(
+                    mResources, a, mResId, 1024, 0);
+            CropView v = a.getCropView();
+            v.setTileSource(source, null);
+            Point wallpaperSize = WallpaperCropActivity.getDefaultWallpaperSize(
+                    a.getResources(), a.getWindowManager());
+            RectF crop = WallpaperCropActivity.getMaxCropRect(
+                    source.getImageWidth(), source.getImageHeight(),
+                    wallpaperSize.x, wallpaperSize.y, false);
+            v.setScale(wallpaperSize.x / crop.width());
+            v.setTouchEnabled(false);
+        }
+        public void onSave(WallpaperPickerActivity a) {
+            boolean finishActivityWhenDone = true;
+            a.cropImageAndSetWallpaper(mResources, mResId, finishActivityWhenDone);
+        }
+        public boolean isSelectable() {
+            return true;
+        }
     }
 
     // called by onCreate; this is subclassed to overwrite WallpaperCropActivity
@@ -151,63 +210,16 @@
                     }
                     return;
                 }
+                WallpaperTileInfo info = (WallpaperTileInfo) v.getTag();
                 if (mSelectedThumb != null) {
                     mSelectedThumb.setSelected(false);
+                    mSelectedThumb = null;
                 }
-
-                ThumbnailMetaData meta = (ThumbnailMetaData) v.getTag();
-                if (meta.mTileType == TileType.WALLPAPER_RESOURCE ||
-                        meta.mTileType == TileType.SAVED_WALLPAPER ||
-                        meta.mTileType == TileType.WALLPAPER_URI) {
+                if (info.isSelectable()) {
                     mSelectedThumb = v;
                     v.setSelected(true);
                 }
-                if (meta.mTileType == TileType.PICK_IMAGE) {
-                    Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
-                    intent.setType("image/*");
-                    Utilities.startActivityForResultSafely(
-                            WallpaperPickerActivity.this, intent, IMAGE_PICK);
-                } else if (meta.mTileType == TileType.WALLPAPER_URI) {
-                    mCropView.setTileSource(new BitmapRegionTileSource(WallpaperPickerActivity.this,
-                            meta.mWallpaperUri, 1024, 0), null);
-                    mCropView.setTouchEnabled(true);
-                } else if (meta.mTileType == TileType.SAVED_WALLPAPER) {
-                    String imageFilename = mSavedImages.getImageFilename(meta.mSavedWallpaperDbId);
-                    File file = new File(getFilesDir(), imageFilename);
-                    mCropView.setTileSource(new BitmapRegionTileSource(WallpaperPickerActivity.this,
-                            file.getAbsolutePath(), 1024, 0), null);
-                    mCropView.moveToLeft();
-                    mCropView.setTouchEnabled(false);
-                } else if (meta.mTileType == TileType.LIVE_WALLPAPER) {
-                    Intent preview = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);
-                    preview.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT,
-                            meta.mLiveWallpaperInfo.info.getComponent());
-                    WallpaperManager wm =
-                            WallpaperManager.getInstance(WallpaperPickerActivity.this);
-                    mLiveWallpaperInfoOnPickerLaunch = wm.getWallpaperInfo();
-                    Utilities.startActivityForResultSafely(WallpaperPickerActivity.this,
-                            preview, PICK_LIVE_WALLPAPER);
-                } else if (meta.mTileType == TileType.WALLPAPER_RESOURCE) {
-                    BitmapRegionTileSource source = new BitmapRegionTileSource(mWallpaperResources,
-                            WallpaperPickerActivity.this, meta.mWallpaperResId, 1024, 0);
-                    mCropView.setTileSource(source, null);
-                    Point wallpaperSize = WallpaperCropActivity.getDefaultWallpaperSize(
-                            getResources(), getWindowManager());
-                    RectF crop = WallpaperCropActivity.getMaxCropRect(
-                            source.getImageWidth(), source.getImageHeight(),
-                            wallpaperSize.x, wallpaperSize.y, false);
-                    mCropView.setScale(wallpaperSize.x / crop.width());
-                    mCropView.setTouchEnabled(false);
-                } else if (meta.mTileType == TileType.THIRD_PARTY_WALLPAPER_PICKER) {
-                    ResolveInfo info = meta.mThirdPartyWallpaperPickerInfo;
-
-                    final ComponentName itemComponentName = new ComponentName(
-                            info.activityInfo.packageName, info.activityInfo.name);
-                    Intent launchIntent = new Intent(Intent.ACTION_SET_WALLPAPER);
-                    launchIntent.setComponent(itemComponentName);
-                    Utilities.startActivityForResultSafely(WallpaperPickerActivity.this,
-                            launchIntent, PICK_WALLPAPER_THIRD_PARTY_ACTIVITY);
-                }
+                info.onClick(WallpaperPickerActivity.this);
             }
         };
         mLongClickListener = new View.OnLongClickListener() {
@@ -231,29 +243,24 @@
         };
 
         // Populate the built-in wallpapers
-        findBundledWallpapers();
+        ArrayList<ResourceWallpaperInfo> wallpapers = findBundledWallpapers();
         mWallpapersView = (LinearLayout) findViewById(R.id.wallpaper_list);
-        ImageAdapter ia = new ImageAdapter(this, mBundledWallpaperThumbs);
-        populateWallpapersFromAdapter(
-                mWallpapersView, ia, mBundledWallpaperResIds, TileType.WALLPAPER_RESOURCE, false, true);
+        BuiltInWallpapersAdapter ia = new BuiltInWallpapersAdapter(this, wallpapers);
+        populateWallpapersFromAdapter(mWallpapersView, ia, false, true);
 
         // Populate the saved wallpapers
         mSavedImages = new SavedWallpaperImages(this);
         mSavedImages.loadThumbnailsAndImageIdList();
-        ArrayList<Drawable> savedWallpaperThumbs = mSavedImages.getThumbnails();
-        ArrayList<Integer > savedWallpaperIds = mSavedImages.getImageIds();
-        ia = new ImageAdapter(this, savedWallpaperThumbs);
-        populateWallpapersFromAdapter(
-                mWallpapersView, ia, savedWallpaperIds, TileType.SAVED_WALLPAPER, true, true);
+        populateWallpapersFromAdapter(mWallpapersView, mSavedImages, true, true);
 
         // Populate the live wallpapers
-        final LinearLayout liveWallpapersView = (LinearLayout) findViewById(R.id.live_wallpaper_list);
+        final LinearLayout liveWallpapersView =
+                (LinearLayout) findViewById(R.id.live_wallpaper_list);
         final LiveWallpaperListAdapter a = new LiveWallpaperListAdapter(this);
         a.registerDataSetObserver(new DataSetObserver() {
             public void onChanged() {
                 liveWallpapersView.removeAllViews();
-                populateWallpapersFromAdapter(
-                        liveWallpapersView, a, null, TileType.LIVE_WALLPAPER, false, false);
+                populateWallpapersFromAdapter(liveWallpapersView, a, false, false);
             }
         });
 
@@ -262,8 +269,7 @@
                 (LinearLayout) findViewById(R.id.third_party_wallpaper_list);
         final ThirdPartyWallpaperPickerListAdapter ta =
                 new ThirdPartyWallpaperPickerListAdapter(this);
-        populateWallpapersFromAdapter(thirdPartyWallpapersView, ta, null,
-                TileType.THIRD_PARTY_WALLPAPER_PICKER, false, false);
+        populateWallpapersFromAdapter(thirdPartyWallpapersView, ta, false, false);
 
         // Add a tile for the Gallery
         LinearLayout masterWallpaperList = (LinearLayout) findViewById(R.id.master_wallpaper_list);
@@ -282,9 +288,8 @@
             galleryThumbnailBg.setColorFilter(colorOverlay, PorterDuff.Mode.SRC_ATOP);
         }
 
-        ThumbnailMetaData meta = new ThumbnailMetaData();
-        meta.mTileType = TileType.PICK_IMAGE;
-        galleryThumbnail.setTag(meta);
+        PickImageInfo pickImageInfo = new PickImageInfo();
+        galleryThumbnail.setTag(pickImageInfo);
         galleryThumbnail.setOnClickListener(mThumbnailOnClickListener);
 
         // Create smooth layout transitions for when items are deleted
@@ -302,28 +307,8 @@
                 new View.OnClickListener() {
                     @Override
                     public void onClick(View v) {
-                        ThumbnailMetaData meta = (ThumbnailMetaData) mSelectedThumb.getTag();
-                        if (meta.mTileType == TileType.PICK_IMAGE) {
-                            // shouldn't be selected, but do nothing
-                        } else if (meta.mWallpaperUri != null) {
-                            boolean finishActivityWhenDone = true;
-                            OnBitmapCroppedHandler h = new OnBitmapCroppedHandler() {
-                                public void onBitmapCropped(byte[] imageBytes) {
-                                    Bitmap thumb = createThumbnail(null, imageBytes, true);
-                                    mSavedImages.writeImage(thumb, imageBytes);
-                                }
-                            };
-                            cropImageAndSetWallpaper(meta.mWallpaperUri, h, finishActivityWhenDone);
-                        } else if (meta.mSavedWallpaperDbId != 0) {
-                            boolean finishActivityWhenDone = true;
-                            String imageFilename =
-                                    mSavedImages.getImageFilename(meta.mSavedWallpaperDbId);
-                            setWallpaper(imageFilename, finishActivityWhenDone);
-                        } else if (meta.mWallpaperResId != 0) {
-                            boolean finishActivityWhenDone = true;
-                            cropImageAndSetWallpaper(mWallpaperResources,
-                                    meta.mWallpaperResId, finishActivityWhenDone);
-                        }
+                        WallpaperTileInfo info = (WallpaperTileInfo) mSelectedThumb.getTag();
+                        info.onSave(WallpaperPickerActivity.this);
                     }
                 });
 
@@ -376,8 +361,7 @@
                         CheckableFrameLayout c =
                                 (CheckableFrameLayout) mWallpapersView.getChildAt(i);
                         if (c.isChecked()) {
-                            ThumbnailMetaData meta = (ThumbnailMetaData) c.getTag();
-                            mSavedImages.deleteImage(meta.mSavedWallpaperDbId);
+                            ((WallpaperTileInfo) c.getTag()).onDelete();
                             viewsToRemove.add(c);
                         }
                     }
@@ -441,34 +425,12 @@
         }
     }
 
-    private enum TileType {
-        PICK_IMAGE,
-        WALLPAPER_RESOURCE,
-        WALLPAPER_URI,
-        SAVED_WALLPAPER,
-        LIVE_WALLPAPER,
-        THIRD_PARTY_WALLPAPER_PICKER
-        };
-
     private void populateWallpapersFromAdapter(ViewGroup parent, BaseAdapter adapter,
-            ArrayList<Integer> imageIds, TileType tileType, boolean addLongPressHandler, boolean selectFirstTile) {
+            boolean addLongPressHandler, boolean selectFirstTile) {
         for (int i = 0; i < adapter.getCount(); i++) {
             FrameLayout thumbnail = (FrameLayout) adapter.getView(i, null, parent);
             parent.addView(thumbnail, i);
-
-            ThumbnailMetaData meta = new ThumbnailMetaData();
-            meta.mTileType = tileType;
-            if (tileType == TileType.WALLPAPER_RESOURCE) {
-                meta.mWallpaperResId = imageIds.get(i);
-            } else if (tileType == TileType.SAVED_WALLPAPER) {
-                meta.mSavedWallpaperDbId = imageIds.get(i);
-            } else if (tileType == TileType.LIVE_WALLPAPER) {
-                meta.mLiveWallpaperInfo =
-                        (LiveWallpaperListAdapter.LiveWallpaperInfo) adapter.getItem(i);
-            } else if (tileType == TileType.THIRD_PARTY_WALLPAPER_PICKER) {
-                meta.mThirdPartyWallpaperPickerInfo = (ResolveInfo) adapter.getItem(i);
-            }
-            thumbnail.setTag(meta);
+            thumbnail.setTag(adapter.getItem(i));
             if (addLongPressHandler) {
                 addLongPressHandler(thumbnail);
             }
@@ -479,16 +441,25 @@
         }
     }
 
-    private Bitmap createThumbnail(Uri uri, byte[] imageBytes, boolean leftAligned) {
-        Resources res = getResources();
-        int width = res.getDimensionPixelSize(R.dimen.wallpaperThumbnailWidth);
-        int height = res.getDimensionPixelSize(R.dimen.wallpaperThumbnailHeight);
+    private static Point getDefaultThumbnailSize(Resources res) {
+        return new Point(res.getDimensionPixelSize(R.dimen.wallpaperThumbnailWidth),
+                res.getDimensionPixelSize(R.dimen.wallpaperThumbnailHeight));
+
+    }
+
+    private static Bitmap createThumbnail(Point size, Context context, Uri uri, byte[] imageBytes,
+            Resources res, int resId, boolean leftAligned) {
+        int width = size.x;
+        int height = size.y;
 
         BitmapCropTask cropTask;
         if (uri != null) {
-            cropTask = new BitmapCropTask(uri, null, width, height, false, true, null);
-        } else {
+            cropTask = new BitmapCropTask(context, uri, null, width, height, false, true, null);
+        } else if (imageBytes != null) {
             cropTask = new BitmapCropTask(imageBytes, null, width, height, false, true, null);
+        }  else {
+            cropTask =
+                    new BitmapCropTask(context, res, resId, null, width, height, false, true, null);
         }
         Point bounds = cropTask.getImageBounds();
         if (bounds == null) {
@@ -515,7 +486,8 @@
 
         // Load the thumbnail
         ImageView image = (ImageView) pickedImageThumbnail.findViewById(R.id.wallpaper_image);
-        Bitmap thumb = createThumbnail(uri, null, false);
+        Point defaultSize = getDefaultThumbnailSize(this.getResources());
+        Bitmap thumb = createThumbnail(defaultSize, this, uri, null, null, 0, false);
         if (thumb != null) {
             image.setImageBitmap(thumb);
             Drawable thumbDrawable = image.getDrawable();
@@ -525,10 +497,8 @@
         }
         mWallpapersView.addView(pickedImageThumbnail, 0);
 
-        ThumbnailMetaData meta = new ThumbnailMetaData();
-        meta.mTileType = TileType.WALLPAPER_URI;
-        meta.mWallpaperUri = uri;
-        pickedImageThumbnail.setTag(meta);
+        UriWallpaperInfo info = new UriWallpaperInfo(uri);
+        pickedImageThumbnail.setTag(info);
         pickedImageThumbnail.setOnClickListener(mThumbnailOnClickListener);
         mThumbnailOnClickListener.onClick(pickedImageThumbnail);
     }
@@ -564,18 +534,24 @@
         v.setOnLongClickListener(mLongClickListener);
     }
 
-    private void findBundledWallpapers() {
-        mBundledWallpaperThumbs = new ArrayList<Drawable>(24);
-        mBundledWallpaperResIds = new ArrayList<Integer>(24);
+    private ArrayList<ResourceWallpaperInfo> findBundledWallpapers() {
+        ArrayList<ResourceWallpaperInfo> bundledWallpapers =
+                new ArrayList<ResourceWallpaperInfo>(24);
 
         Pair<ApplicationInfo, Integer> r = getWallpaperArrayResourceId();
         if (r != null) {
             try {
-                mWallpaperResources = getPackageManager().getResourcesForApplication(r.first);
-                addWallpapers(mWallpaperResources, r.first.packageName, r.second);
+                Resources wallpaperRes = getPackageManager().getResourcesForApplication(r.first);
+                bundledWallpapers = addWallpapers(wallpaperRes, r.first.packageName, r.second);
             } catch (PackageManager.NameNotFoundException e) {
             }
         }
+        //TODO: add default wallpaper
+        //Resources sysRes = Resources.getSystem();
+        //int resId = sysRes.getIdentifier("default_wallpaper", "drawable", "android");
+        //bundledWallpapers.add(
+        //        new ResourceWallpaperInfo(sysRes, resId, new ColorDrawable(0xFFFF0000)));
+        return bundledWallpapers;
     }
 
     public Pair<ApplicationInfo, Integer> getWallpaperArrayResourceId() {
@@ -592,23 +568,39 @@
         }
     }
 
-    private void addWallpapers(Resources resources, String packageName, int listResId) {
-        final String[] extras = resources.getStringArray(listResId);
+    private ArrayList<ResourceWallpaperInfo> addWallpapers(
+            Resources res, String packageName, int listResId) {
+        ArrayList<ResourceWallpaperInfo> bundledWallpapers =
+                new ArrayList<ResourceWallpaperInfo>(24);
+        final String[] extras = res.getStringArray(listResId);
         for (String extra : extras) {
-            int res = resources.getIdentifier(extra, "drawable", packageName);
-            if (res != 0) {
-                final int thumbRes = resources.getIdentifier(extra + "_small",
-                        "drawable", packageName);
+            int resId = res.getIdentifier(extra, "drawable", packageName);
+            if (resId != 0) {
+                final int thumbRes = res.getIdentifier(extra + "_small", "drawable", packageName);
 
                 if (thumbRes != 0) {
-                    mBundledWallpaperThumbs.add(resources.getDrawable(thumbRes));
-                    mBundledWallpaperResIds.add(res);
+                    ResourceWallpaperInfo wallpaperInfo =
+                            new ResourceWallpaperInfo(res, resId, res.getDrawable(thumbRes));
+                    bundledWallpapers.add(wallpaperInfo);
                     // Log.d(TAG, "add: [" + packageName + "]: " + extra + " (" + res + ")");
                 }
             } else {
                 Log.e(TAG, "Couldn't find wallpaper " + extra);
             }
         }
+        return bundledWallpapers;
+    }
+
+    public CropView getCropView() {
+        return mCropView;
+    }
+
+    public SavedWallpaperImages getSavedImages() {
+        return mSavedImages;
+    }
+
+    public void onLiveWallpaperPickerLaunch() {
+        mLiveWallpaperInfoOnPickerLaunch = WallpaperManager.getInstance(this).getWallpaperInfo();
     }
 
     static class ZeroPaddingDrawable extends LevelListDrawable {
@@ -625,21 +617,21 @@
         }
     }
 
-    private static class ImageAdapter extends BaseAdapter implements ListAdapter, SpinnerAdapter {
+    private static class BuiltInWallpapersAdapter extends BaseAdapter implements ListAdapter {
         private LayoutInflater mLayoutInflater;
-        private ArrayList<Drawable> mThumbs;
+        private ArrayList<ResourceWallpaperInfo> mWallpapers;
 
-        ImageAdapter(Activity activity, ArrayList<Drawable> thumbs) {
+        BuiltInWallpapersAdapter(Activity activity, ArrayList<ResourceWallpaperInfo> wallpapers) {
             mLayoutInflater = activity.getLayoutInflater();
-            mThumbs = thumbs;
+            mWallpapers = wallpapers;
         }
 
         public int getCount() {
-            return mThumbs.size();
+            return mWallpapers.size();
         }
 
-        public Object getItem(int position) {
-            return position;
+        public ResourceWallpaperInfo getItem(int position) {
+            return mWallpapers.get(position);
         }
 
         public long getItemId(int position) {
@@ -647,27 +639,33 @@
         }
 
         public View getView(int position, View convertView, ViewGroup parent) {
-            View view;
-
-            if (convertView == null) {
-                view = mLayoutInflater.inflate(R.layout.wallpaper_picker_item, parent, false);
-            } else {
-                view = convertView;
-            }
-
-            setWallpaperItemPaddingToZero((FrameLayout) view);
-
-            ImageView image = (ImageView) view.findViewById(R.id.wallpaper_image);
-
-            Drawable thumbDrawable = mThumbs.get(position);
-            if (thumbDrawable != null) {
-                image.setImageDrawable(thumbDrawable);
-                thumbDrawable.setDither(true);
-            } else {
+            Drawable thumb = mWallpapers.get(position).mThumb;
+            if (thumb == null) {
                 Log.e(TAG, "Error decoding thumbnail for wallpaper #" + position);
             }
-
-            return view;
+            return createImageTileView(mLayoutInflater, position, convertView, parent, thumb);
         }
     }
+
+    public static View createImageTileView(LayoutInflater layoutInflater, int position,
+            View convertView, ViewGroup parent, Drawable thumb) {
+        View view;
+
+        if (convertView == null) {
+            view = layoutInflater.inflate(R.layout.wallpaper_picker_item, parent, false);
+        } else {
+            view = convertView;
+        }
+
+        setWallpaperItemPaddingToZero((FrameLayout) view);
+
+        ImageView image = (ImageView) view.findViewById(R.id.wallpaper_image);
+
+        if (thumb != null) {
+            image.setImageDrawable(thumb);
+            thumb.setDither(true);
+        }
+
+        return view;
+    }
 }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 063a256..ada41a0 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -1846,10 +1846,10 @@
         final boolean stateIsSpringLoaded = (state == State.SPRING_LOADED);
         final boolean stateIsSmall = (state == State.SMALL);
         final boolean stateIsOverview = (state == State.OVERVIEW);
-        float finalBackgroundAlpha = stateIsSpringLoaded ? 1.0f : 0f;
+        float finalBackgroundAlpha = (stateIsSpringLoaded || stateIsOverview) ? 1.0f : 0f;
         float finalHotseatAndPageIndicatorAlpha = (stateIsOverview || stateIsSmall) ? 0f : 1f;
         float finalOverviewPanelAlpha = stateIsOverview ? 1f : 0f;
-        float finalSearchBarAlpha = stateIsOverview ? 0f : 1f;
+        float finalSearchBarAlpha = !stateIsNormal ? 0f : 1f;
         float finalWorkspaceTranslationY = stateIsOverview ? getOverviewModeTranslationY() : 0;
 
         boolean zoomIn = true;
@@ -1872,8 +1872,6 @@
             if (oldStateIsNormal && stateIsSmall) {
                 zoomIn = false;
                 updateChildrenLayersEnabled(false);
-            } else {
-                finalBackgroundAlpha = 1.0f;
             }
         }
         final int duration = zoomIn ?
@@ -1881,24 +1879,9 @@
                 getResources().getInteger(R.integer.config_appsCustomizeWorkspaceShrinkTime);
         for (int i = 0; i < getChildCount(); i++) {
             final CellLayout cl = (CellLayout) getChildAt(i);
-            float finalAlpha = (!mWorkspaceFadeInAdjacentScreens || stateIsSpringLoaded ||
-                    (i == mCurrentPage)) ? 1f : 0f;
-            float currentAlpha = cl.getShortcutsAndWidgets().getAlpha();
-            float initialAlpha = currentAlpha;
-
-            // Determine the pages alpha during the state transition
-            if ((oldStateIsSmall && stateIsNormal) ||
-                (oldStateIsNormal && stateIsSmall)) {
-                // To/from workspace - only show the current page unless the transition is not
-                //                     animated and the animation end callback below doesn't run;
-                //                     or, if we're in spring-loaded mode
-                if (i == mCurrentPage || !animated || oldStateIsSpringLoaded) {
-                    finalAlpha = 1f;
-                } else {
-                    initialAlpha = 0f;
-                    finalAlpha = 0f;
-                }
-            }
+            float finalAlpha = (!mWorkspaceFadeInAdjacentScreens ||
+                    (i == mCurrentPage)) && !stateIsSmall ? 1f : 0f;
+            float initialAlpha = cl.getShortcutsAndWidgets().getAlpha();
 
             mOldAlphas[i] = initialAlpha;
             mNewAlphas[i] = finalAlpha;
@@ -1929,7 +1912,6 @@
                     cl.setBackgroundAlpha(mNewBackgroundAlphas[i]);
                     cl.setShortcutAndWidgetAlpha(mNewAlphas[i]);
                 } else {
-
                     if (mOldAlphas[i] != mNewAlphas[i] || currentAlpha != mNewAlphas[i]) {
                         LauncherViewPropertyAnimator alphaAnim =
                             new LauncherViewPropertyAnimator(cl.getShortcutsAndWidgets());
@@ -2932,7 +2914,10 @@
        mTempPt[0] = x;
        mTempPt[1] = y;
        mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true);
-       mLauncher.getHotseat().getHitRect(r);
+
+       LauncherAppState app = LauncherAppState.getInstance();
+       DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+       r = grid.getHotseatRect();
        if (r.contains(mTempPt[0], mTempPt[1])) {
            return true;
        }