Merge "Moving a few testing classes to a separate package" into ub-launcher3-master
diff --git a/.gitignore b/.gitignore
index aea5d61..7240e48 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,9 @@
 WallpaperPicker/gen/
 WallpaperPicker/.project.properties
 bin/
+.idea/
+.gradle/
+local.properties
+gradle/
+build/
+gradlew*
\ No newline at end of file
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 93bff5e..1f908d6 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -20,7 +20,7 @@
 <manifest
     xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.launcher3">
-    <uses-sdk android:targetSdkVersion="21" android:minSdkVersion="16"/>
+    <uses-sdk android:targetSdkVersion="23" android:minSdkVersion="16"/>
 
     <permission
         android:name="com.android.launcher.permission.INSTALL_SHORTCUT"
diff --git a/WallpaperPicker/res/anim/fade_out.xml b/WallpaperPicker/res/anim/fade_out.xml
new file mode 100644
index 0000000..9ca7407
--- /dev/null
+++ b/WallpaperPicker/res/anim/fade_out.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<!-- startOffset is the same as the duration of the wallpaper_enter animation. We have this delay so
+    that we don't see the wallpaper changing before fading back to the home screen. -->
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+    android:startOffset="@android:integer/config_longAnimTime"
+    android:duration="@android:integer/config_mediumAnimTime"
+    android:fromAlpha="1"
+    android:toAlpha="0"/>
\ No newline at end of file
diff --git a/WallpaperPicker/res/values-v19/styles.xml b/WallpaperPicker/res/values-v19/styles.xml
index 136cf01..15fb0ea 100644
--- a/WallpaperPicker/res/values-v19/styles.xml
+++ b/WallpaperPicker/res/values-v19/styles.xml
@@ -25,7 +25,7 @@
         <item name="android:windowTranslucentNavigation">true</item>
     </style>
 
-    <style name="Theme" parent="@android:style/Theme.DeviceDefault.Wallpaper.NoTitleBar">
+    <style name="Theme" parent="@style/BaseWallpaperTheme">
         <item name="android:windowTranslucentStatus">true</item>
         <item name="android:windowTranslucentNavigation">true</item>
     </style>
diff --git a/WallpaperPicker/res/values-v21/styles.xml b/WallpaperPicker/res/values-v21/styles.xml
index 582ab8f..70220ed 100644
--- a/WallpaperPicker/res/values-v21/styles.xml
+++ b/WallpaperPicker/res/values-v21/styles.xml
@@ -33,4 +33,11 @@
         <item name="android:background">?android:attr/selectableItemBackgroundBorderless</item>
     </style>
 
+    <style name="Theme" parent="@style/BaseWallpaperTheme">
+        <item name="android:windowTranslucentStatus">true</item>
+        <item name="android:windowTranslucentNavigation">true</item>
+        <item name="android:colorControlActivated">@color/launcher_accent_color</item>
+        <item name="android:colorAccent">@color/launcher_accent_color</item>
+        <item name="android:colorPrimary">@color/launcher_accent_color</item>
+    </style>
 </resources>
\ No newline at end of file
diff --git a/WallpaperPicker/res/values/colors.xml b/WallpaperPicker/res/values/colors.xml
index adae7cf..6ba32f0 100644
--- a/WallpaperPicker/res/values/colors.xml
+++ b/WallpaperPicker/res/values/colors.xml
@@ -19,4 +19,6 @@
 -->
 <resources>
     <color name="wallpaper_picker_translucent_gray">#66000000</color>
+
+    <color name="launcher_accent_color">#ff009688</color>
 </resources>
diff --git a/WallpaperPicker/res/values/styles.xml b/WallpaperPicker/res/values/styles.xml
index 74aeab9..d1c945a 100644
--- a/WallpaperPicker/res/values/styles.xml
+++ b/WallpaperPicker/res/values/styles.xml
@@ -35,9 +35,15 @@
         <item name="android:background">#88000000</item>
     </style>
 
-    <style name="Theme" parent="@android:style/Theme.DeviceDefault.Wallpaper.NoTitleBar">
+    <style name="BaseWallpaperTheme" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar">
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:colorBackgroundCacheHint">@null</item>
+        <item name="android:windowShowWallpaper">true</item>
+        <item name="android:windowNoTitle">true</item>
     </style>
 
+    <style name="Theme" parent="@style/BaseWallpaperTheme"></style>
+
     <style name="ActionBarSetWallpaperStyle" parent="@android:style/Widget.DeviceDefault.ActionButton">
         <item name="android:textColor">#ffffffff</item>
         <item name="android:background">?android:attr/selectableItemBackground</item>
diff --git a/WallpaperPicker/src/com/android/gallery3d/common/BitmapCropTask.java b/WallpaperPicker/src/com/android/gallery3d/common/BitmapCropTask.java
index 45118bf..1d8e37d 100644
--- a/WallpaperPicker/src/com/android/gallery3d/common/BitmapCropTask.java
+++ b/WallpaperPicker/src/com/android/gallery3d/common/BitmapCropTask.java
@@ -45,6 +45,10 @@
         public void onBitmapCropped(byte[] imageBytes);
     }
 
+    public interface OnEndCropHandler {
+        public void run(boolean cropSucceeded);
+    }
+
     private static final int DEFAULT_COMPRESS_QUALITY = 90;
     private static final String LOGTAG = "BitmapCropTask";
 
@@ -59,56 +63,56 @@
     boolean mSetWallpaper;
     boolean mSaveCroppedBitmap;
     Bitmap mCroppedBitmap;
-    Runnable mOnEndRunnable;
+    BitmapCropTask.OnEndCropHandler mOnEndCropHandler;
     Resources mResources;
     BitmapCropTask.OnBitmapCroppedHandler mOnBitmapCroppedHandler;
     boolean mNoCrop;
 
     public BitmapCropTask(Context c, String filePath,
             RectF cropBounds, int rotation, int outWidth, int outHeight,
-            boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
+            boolean setWallpaper, boolean saveCroppedBitmap, OnEndCropHandler onEndCropHandler) {
         mContext = c;
         mInFilePath = filePath;
         init(cropBounds, rotation,
-                outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
+                outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndCropHandler);
     }
 
     public BitmapCropTask(byte[] imageBytes,
             RectF cropBounds, int rotation, int outWidth, int outHeight,
-            boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
+            boolean setWallpaper, boolean saveCroppedBitmap, OnEndCropHandler onEndCropHandler) {
         mInImageBytes = imageBytes;
         init(cropBounds, rotation,
-                outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
+                outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndCropHandler);
     }
 
     public BitmapCropTask(Context c, Uri inUri,
             RectF cropBounds, int rotation, int outWidth, int outHeight,
-            boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
+            boolean setWallpaper, boolean saveCroppedBitmap, OnEndCropHandler onEndCropHandler) {
         mContext = c;
         mInUri = inUri;
         init(cropBounds, rotation,
-                outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
+                outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndCropHandler);
     }
 
     public BitmapCropTask(Context c, Resources res, int inResId,
             RectF cropBounds, int rotation, int outWidth, int outHeight,
-            boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
+            boolean setWallpaper, boolean saveCroppedBitmap, OnEndCropHandler onEndCropHandler) {
         mContext = c;
         mInResId = inResId;
         mResources = res;
         init(cropBounds, rotation,
-                outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
+                outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndCropHandler);
     }
 
     private void init(RectF cropBounds, int rotation, int outWidth, int outHeight,
-            boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
+            boolean setWallpaper, boolean saveCroppedBitmap, OnEndCropHandler onEndCropHandler) {
         mCropBounds = cropBounds;
         mRotation = rotation;
         mOutWidth = outWidth;
         mOutHeight = outHeight;
         mSetWallpaper = setWallpaper;
         mSaveCroppedBitmap = saveCroppedBitmap;
-        mOnEndRunnable = onEndRunnable;
+        mOnEndCropHandler = onEndCropHandler;
     }
 
     public void setOnBitmapCropped(BitmapCropTask.OnBitmapCroppedHandler handler) {
@@ -119,8 +123,8 @@
         mNoCrop = value;
     }
 
-    public void setOnEndRunnable(Runnable onEndRunnable) {
-        mOnEndRunnable = onEndRunnable;
+    public void setOnEndCropHandler(OnEndCropHandler onEndCropHandler) {
+        mOnEndCropHandler = onEndCropHandler;
     }
 
     // Helper to setup input stream
@@ -398,8 +402,8 @@
 
     @Override
     protected void onPostExecute(Boolean result) {
-        if (mOnEndRunnable != null) {
-            mOnEndRunnable.run();
+        if (mOnEndCropHandler != null) {
+            mOnEndCropHandler.run(result);
         }
     }
 }
\ No newline at end of file
diff --git a/WallpaperPicker/src/com/android/gallery3d/glrenderer/BasicTexture.java b/WallpaperPicker/src/com/android/gallery3d/glrenderer/BasicTexture.java
index 0f3efb7..7270e88 100644
--- a/WallpaperPicker/src/com/android/gallery3d/glrenderer/BasicTexture.java
+++ b/WallpaperPicker/src/com/android/gallery3d/glrenderer/BasicTexture.java
@@ -46,8 +46,6 @@
     protected int mTextureWidth;
     protected int mTextureHeight;
 
-    private boolean mHasBorder;
-
     protected GLCanvas mCanvasRef = null;
     private static WeakHashMap<BasicTexture, Object> sAllTextures
             = new WeakHashMap<BasicTexture, Object>();
@@ -85,10 +83,6 @@
         }
     }
 
-    public boolean isFlippedVertically() {
-      return false;
-    }
-
     public int getId() {
         return mId;
     }
@@ -113,25 +107,6 @@
         return mTextureHeight;
     }
 
-    // Returns true if the texture has one pixel transparent border around the
-    // actual content. This is used to avoid jigged edges.
-    //
-    // The jigged edges appear because we use GL_CLAMP_TO_EDGE for texture wrap
-    // mode (GL_CLAMP is not available in OpenGL ES), so a pixel partially
-    // covered by the texture will use the color of the edge texel. If we add
-    // the transparent border, the color of the edge texel will be mixed with
-    // appropriate amount of transparent.
-    //
-    // Currently our background is black, so we can draw the thumbnails without
-    // enabling blending.
-    public boolean hasBorder() {
-        return mHasBorder;
-    }
-
-    protected void setBorder(boolean hasBorder) {
-        mHasBorder = hasBorder;
-    }
-
     @Override
     public void draw(GLCanvas canvas, int x, int y) {
         canvas.drawTexture(this, x, y, getWidth(), getHeight());
@@ -146,9 +121,6 @@
     // It should make sure the data is uploaded to GL memory.
     abstract protected boolean onBind(GLCanvas canvas);
 
-    // Returns the GL texture target for this texture (e.g. GL_TEXTURE_2D).
-    abstract protected int getTarget();
-
     public boolean isLoaded() {
         return mState == STATE_LOADED;
     }
@@ -185,13 +157,6 @@
         sInFinalizer.set(null);
     }
 
-    // This is for deciding if we can call Bitmap's recycle().
-    // We cannot call Bitmap's recycle() in finalizer because at that point
-    // the finalizer of Bitmap may already be called so recycle() will crash.
-    public static boolean inFinalizer() {
-        return sInFinalizer.get() != null;
-    }
-
     public static void yieldAllTextures() {
         synchronized (sAllTextures) {
             for (BasicTexture t : sAllTextures.keySet()) {
diff --git a/WallpaperPicker/src/com/android/gallery3d/glrenderer/BitmapTexture.java b/WallpaperPicker/src/com/android/gallery3d/glrenderer/BitmapTexture.java
index f8b01cb..bb69b68 100644
--- a/WallpaperPicker/src/com/android/gallery3d/glrenderer/BitmapTexture.java
+++ b/WallpaperPicker/src/com/android/gallery3d/glrenderer/BitmapTexture.java
@@ -29,11 +29,7 @@
     protected Bitmap mContentBitmap;
 
     public BitmapTexture(Bitmap bitmap) {
-        this(bitmap, false);
-    }
-
-    public BitmapTexture(Bitmap bitmap, boolean hasBorder) {
-        super(hasBorder);
+        super();
         Utils.assertTrue(bitmap != null && !bitmap.isRecycled());
         mContentBitmap = bitmap;
     }
diff --git a/WallpaperPicker/src/com/android/gallery3d/glrenderer/GLCanvas.java b/WallpaperPicker/src/com/android/gallery3d/glrenderer/GLCanvas.java
index 5b07477..2bda8d2 100644
--- a/WallpaperPicker/src/com/android/gallery3d/glrenderer/GLCanvas.java
+++ b/WallpaperPicker/src/com/android/gallery3d/glrenderer/GLCanvas.java
@@ -17,7 +17,6 @@
 package com.android.gallery3d.glrenderer;
 
 import android.graphics.Bitmap;
-import android.graphics.Rect;
 import android.graphics.RectF;
 
 //
@@ -40,36 +39,14 @@
     // Clear the drawing buffers. This should only be used by GLRoot.
     public abstract void clearBuffer();
 
-    public abstract void clearBuffer(float[] argb);
-
-    // Sets and gets the current alpha, alpha must be in [0, 1].
-    public abstract void setAlpha(float alpha);
-
-    public abstract float getAlpha();
-
-    // (current alpha) = (current alpha) * alpha
-    public abstract void multiplyAlpha(float alpha);
-
-    // Change the current transform matrix.
-    public abstract void translate(float x, float y, float z);
-
     public abstract void translate(float x, float y);
 
-    public abstract void scale(float sx, float sy, float sz);
-
     public abstract void rotate(float angle, float x, float y, float z);
 
-    public abstract void multiplyMatrix(float[] mMatrix, int offset);
-
-    // Pushes the configuration state (matrix, and alpha) onto
-    // a private stack.
-    public abstract void save();
-
     // Same as save(), but only save those specified in saveFlags.
     public abstract void save(int saveFlags);
 
     public static final int SAVE_FLAG_ALL = 0xFFFFFFFF;
-    public static final int SAVE_FLAG_ALPHA = 0x01;
     public static final int SAVE_FLAG_MATRIX = 0x02;
 
     // Pops from the top of the stack as current configuration state (matrix,
@@ -78,64 +55,22 @@
     // last save call.
     public abstract void restore();
 
-    // Draws a line using the specified paint from (x1, y1) to (x2, y2).
-    // (Both end points are included).
-    public abstract void drawLine(float x1, float y1, float x2, float y2, GLPaint paint);
-
-    // Draws a rectangle using the specified paint from (x1, y1) to (x2, y2).
-    // (Both end points are included).
-    public abstract void drawRect(float x1, float y1, float x2, float y2, GLPaint paint);
-
-    // Fills the specified rectangle with the specified color.
-    public abstract void fillRect(float x, float y, float width, float height, int color);
-
     // Draws a texture to the specified rectangle.
-    public abstract void drawTexture(
-            BasicTexture texture, int x, int y, int width, int height);
-
-    public abstract void drawMesh(BasicTexture tex, int x, int y, int xyBuffer,
-            int uvBuffer, int indexBuffer, int indexCount);
+    public abstract void drawTexture(BasicTexture texture, int x, int y, int width, int height);
 
     // Draws the source rectangle part of the texture to the target rectangle.
     public abstract void drawTexture(BasicTexture texture, RectF source, RectF target);
 
-    // Draw a texture with a specified texture transform.
-    public abstract void drawTexture(BasicTexture texture, float[] mTextureTransform,
-                int x, int y, int w, int h);
-
-    // Draw two textures to the specified rectangle. The actual texture used is
-    // from * (1 - ratio) + to * ratio
-    // The two textures must have the same size.
-    public abstract void drawMixed(BasicTexture from, int toColor,
-            float ratio, int x, int y, int w, int h);
-
-    // Draw a region of a texture and a specified color to the specified
-    // rectangle. The actual color used is from * (1 - ratio) + to * ratio.
-    // The region of the texture is defined by parameter "src". The target
-    // rectangle is specified by parameter "target".
-    public abstract void drawMixed(BasicTexture from, int toColor,
-            float ratio, RectF src, RectF target);
-
     // Unloads the specified texture from the canvas. The resource allocated
     // to draw the texture will be released. The specified texture will return
     // to the unloaded state. This function should be called only from
     // BasicTexture or its descendant
     public abstract boolean unloadTexture(BasicTexture texture);
 
-    // Delete the specified buffer object, similar to unloadTexture.
-    public abstract void deleteBuffer(int bufferId);
-
     // Delete the textures and buffers in GL side. This function should only be
     // called in the GL thread.
     public abstract void deleteRecycledResources();
 
-    // Dump statistics information and clear the counters. For debug only.
-    public abstract void dumpStatisticsAndClear();
-
-    public abstract void beginRenderTarget(RawTexture texture);
-
-    public abstract void endRenderTarget();
-
     /**
      * Sets texture parameters to use GL_CLAMP_TO_EDGE for both
      * GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T. Sets texture parameters to be
@@ -185,31 +120,4 @@
      * @return The buffer ID that was generated.
      */
     public abstract int uploadBuffer(java.nio.FloatBuffer buffer);
-
-    /**
-     * Generates buffers and uploads the element array buffer data.
-     *
-     * @param buffer The buffer to upload
-     * @return The buffer ID that was generated.
-     */
-    public abstract int uploadBuffer(java.nio.ByteBuffer buffer);
-
-    /**
-     * After LightCycle makes GL calls, this method is called to restore the GL
-     * configuration to the one expected by GLCanvas.
-     */
-    public abstract void recoverFromLightCycle();
-
-    /**
-     * Gets the bounds given by x, y, width, and height as well as the internal
-     * matrix state. There is no special handling for non-90-degree rotations.
-     * It only considers the lower-left and upper-right corners as the bounds.
-     *
-     * @param bounds The output bounds to write to.
-     * @param x The left side of the input rectangle.
-     * @param y The bottom of the input rectangle.
-     * @param width The width of the input rectangle.
-     * @param height The height of the input rectangle.
-     */
-    public abstract void getBounds(Rect bounds, int x, int y, int width, int height);
 }
diff --git a/WallpaperPicker/src/com/android/gallery3d/glrenderer/GLES20Canvas.java b/WallpaperPicker/src/com/android/gallery3d/glrenderer/GLES20Canvas.java
index 933260b..0da3bae 100644
--- a/WallpaperPicker/src/com/android/gallery3d/glrenderer/GLES20Canvas.java
+++ b/WallpaperPicker/src/com/android/gallery3d/glrenderer/GLES20Canvas.java
@@ -16,7 +16,6 @@
 package com.android.gallery3d.glrenderer;
 
 import android.graphics.Bitmap;
-import android.graphics.Rect;
 import android.graphics.RectF;
 import android.opengl.GLES20;
 import android.opengl.GLUtils;
@@ -27,24 +26,22 @@
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.FloatBuffer;
-import java.util.ArrayList;
 import java.util.Arrays;
 
+import javax.microedition.khronos.opengles.GL11;
+
 public class GLES20Canvas implements GLCanvas {
     // ************** Constants **********************
     private static final String TAG = GLES20Canvas.class.getSimpleName();
     private static final int FLOAT_SIZE = Float.SIZE / Byte.SIZE;
-    private static final float OPAQUE_ALPHA = 0.95f;
 
     private static final int COORDS_PER_VERTEX = 2;
     private static final int VERTEX_STRIDE = COORDS_PER_VERTEX * FLOAT_SIZE;
 
     private static final int COUNT_FILL_VERTEX = 4;
-    private static final int COUNT_LINE_VERTEX = 2;
-    private static final int COUNT_RECT_VERTEX = 4;
     private static final int OFFSET_FILL_RECT = 0;
-    private static final int OFFSET_DRAW_LINE = OFFSET_FILL_RECT + COUNT_FILL_VERTEX;
-    private static final int OFFSET_DRAW_RECT = OFFSET_DRAW_LINE + COUNT_LINE_VERTEX;
+
+    private static final int GL_TARGET = GL11.GL_TEXTURE_2D;
 
     private static final float[] BOX_COORDINATES = {
             0, 0, // Fill rectangle
@@ -59,33 +56,11 @@
             1, 0,
     };
 
-    private static final float[] BOUNDS_COORDINATES = {
-        0, 0, 0, 1,
-        1, 1, 0, 1,
-    };
-
     private static final String POSITION_ATTRIBUTE = "aPosition";
-    private static final String COLOR_UNIFORM = "uColor";
     private static final String MATRIX_UNIFORM = "uMatrix";
     private static final String TEXTURE_MATRIX_UNIFORM = "uTextureMatrix";
     private static final String TEXTURE_SAMPLER_UNIFORM = "uTextureSampler";
     private static final String ALPHA_UNIFORM = "uAlpha";
-    private static final String TEXTURE_COORD_ATTRIBUTE = "aTextureCoordinate";
-
-    private static final String DRAW_VERTEX_SHADER = ""
-            + "uniform mat4 " + MATRIX_UNIFORM + ";\n"
-            + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n"
-            + "void main() {\n"
-            + "  vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n"
-            + "  gl_Position = " + MATRIX_UNIFORM + " * pos;\n"
-            + "}\n";
-
-    private static final String DRAW_FRAGMENT_SHADER = ""
-            + "precision mediump float;\n"
-            + "uniform vec4 " + COLOR_UNIFORM + ";\n"
-            + "void main() {\n"
-            + "  gl_FragColor = " + COLOR_UNIFORM + ";\n"
-            + "}\n";
 
     private static final String TEXTURE_VERTEX_SHADER = ""
             + "uniform mat4 " + MATRIX_UNIFORM + ";\n"
@@ -98,17 +73,6 @@
             + "  vTextureCoord = (" + TEXTURE_MATRIX_UNIFORM + " * pos).xy;\n"
             + "}\n";
 
-    private static final String MESH_VERTEX_SHADER = ""
-            + "uniform mat4 " + MATRIX_UNIFORM + ";\n"
-            + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n"
-            + "attribute vec2 " + TEXTURE_COORD_ATTRIBUTE + ";\n"
-            + "varying vec2 vTextureCoord;\n"
-            + "void main() {\n"
-            + "  vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n"
-            + "  gl_Position = " + MATRIX_UNIFORM + " * pos;\n"
-            + "  vTextureCoord = " + TEXTURE_COORD_ATTRIBUTE + ";\n"
-            + "}\n";
-
     private static final String TEXTURE_FRAGMENT_SHADER = ""
             + "precision mediump float;\n"
             + "varying vec2 vTextureCoord;\n"
@@ -119,26 +83,13 @@
             + "  gl_FragColor *= " + ALPHA_UNIFORM + ";\n"
             + "}\n";
 
-    private static final String OES_TEXTURE_FRAGMENT_SHADER = ""
-            + "#extension GL_OES_EGL_image_external : require\n"
-            + "precision mediump float;\n"
-            + "varying vec2 vTextureCoord;\n"
-            + "uniform float " + ALPHA_UNIFORM + ";\n"
-            + "uniform samplerExternalOES " + TEXTURE_SAMPLER_UNIFORM + ";\n"
-            + "void main() {\n"
-            + "  gl_FragColor = texture2D(" + TEXTURE_SAMPLER_UNIFORM + ", vTextureCoord);\n"
-            + "  gl_FragColor *= " + ALPHA_UNIFORM + ";\n"
-            + "}\n";
-
     private static final int INITIAL_RESTORE_STATE_SIZE = 8;
     private static final int MATRIX_SIZE = 16;
 
     // Keep track of restore state
     private float[] mMatrices = new float[INITIAL_RESTORE_STATE_SIZE * MATRIX_SIZE];
-    private float[] mAlphas = new float[INITIAL_RESTORE_STATE_SIZE];
     private IntArray mSaveFlags = new IntArray();
 
-    private int mCurrentAlphaIndex = 0;
     private int mCurrentMatrixIndex = 0;
 
     // Viewport size
@@ -148,15 +99,8 @@
     // Projection matrix
     private float[] mProjectionMatrix = new float[MATRIX_SIZE];
 
-    // Screen size for when we aren't bound to a texture
-    private int mScreenWidth;
-    private int mScreenHeight;
-
     // GL programs
-    private int mDrawProgram;
     private int mTextureProgram;
-    private int mOesTextureProgram;
-    private int mMeshProgram;
 
     // GL buffer containing BOX_COORDINATES
     private int mBoxCoordinates;
@@ -165,17 +109,11 @@
     private static final int INDEX_POSITION = 0;
     private static final int INDEX_MATRIX = 1;
 
-    // Handle indices -- draw
-    private static final int INDEX_COLOR = 2;
-
     // Handle indices -- texture
     private static final int INDEX_TEXTURE_MATRIX = 2;
     private static final int INDEX_TEXTURE_SAMPLER = 3;
     private static final int INDEX_ALPHA = 4;
 
-    // Handle indices -- mesh
-    private static final int INDEX_TEXTURE_COORD = 2;
-
     private abstract static class ShaderParameter {
         public int handle;
         protected final String mName;
@@ -211,52 +149,18 @@
         }
     }
 
-    ShaderParameter[] mDrawParameters = {
-            new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION
-            new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX
-            new UniformShaderParameter(COLOR_UNIFORM), // INDEX_COLOR
-    };
-    ShaderParameter[] mTextureParameters = {
+    private ShaderParameter[] mTextureParameters = {
             new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION
             new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX
             new UniformShaderParameter(TEXTURE_MATRIX_UNIFORM), // INDEX_TEXTURE_MATRIX
             new UniformShaderParameter(TEXTURE_SAMPLER_UNIFORM), // INDEX_TEXTURE_SAMPLER
             new UniformShaderParameter(ALPHA_UNIFORM), // INDEX_ALPHA
     };
-    ShaderParameter[] mOesTextureParameters = {
-            new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION
-            new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX
-            new UniformShaderParameter(TEXTURE_MATRIX_UNIFORM), // INDEX_TEXTURE_MATRIX
-            new UniformShaderParameter(TEXTURE_SAMPLER_UNIFORM), // INDEX_TEXTURE_SAMPLER
-            new UniformShaderParameter(ALPHA_UNIFORM), // INDEX_ALPHA
-    };
-    ShaderParameter[] mMeshParameters = {
-            new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION
-            new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX
-            new AttributeShaderParameter(TEXTURE_COORD_ATTRIBUTE), // INDEX_TEXTURE_COORD
-            new UniformShaderParameter(TEXTURE_SAMPLER_UNIFORM), // INDEX_TEXTURE_SAMPLER
-            new UniformShaderParameter(ALPHA_UNIFORM), // INDEX_ALPHA
-    };
 
     private final IntArray mUnboundTextures = new IntArray();
-    private final IntArray mDeleteBuffers = new IntArray();
-
-    // Keep track of statistics for debugging
-    private int mCountDrawMesh = 0;
-    private int mCountTextureRect = 0;
-    private int mCountFillRect = 0;
-    private int mCountDrawLine = 0;
-
-    // Buffer for framebuffer IDs -- we keep track so we can switch the attached
-    // texture.
-    private int[] mFrameBuffer = new int[1];
-
-    // Bound textures.
-    private ArrayList<RawTexture> mTargetTextures = new ArrayList<RawTexture>();
 
     // Temporary variables used within calculations
     private final float[] mTempMatrix = new float[32];
-    private final float[] mTempColor = new float[4];
     private final RectF mTempSourceRect = new RectF();
     private final RectF mTempTargetRect = new RectF();
     private final float[] mTempTextureMatrix = new float[MATRIX_SIZE];
@@ -267,26 +171,15 @@
     public GLES20Canvas() {
         Matrix.setIdentityM(mTempTextureMatrix, 0);
         Matrix.setIdentityM(mMatrices, mCurrentMatrixIndex);
-        mAlphas[mCurrentAlphaIndex] = 1f;
-        mTargetTextures.add(null);
 
         FloatBuffer boxBuffer = createBuffer(BOX_COORDINATES);
         mBoxCoordinates = uploadBuffer(boxBuffer);
 
-        int drawVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, DRAW_VERTEX_SHADER);
         int textureVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, TEXTURE_VERTEX_SHADER);
-        int meshVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, MESH_VERTEX_SHADER);
-        int drawFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, DRAW_FRAGMENT_SHADER);
         int textureFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, TEXTURE_FRAGMENT_SHADER);
-        int oesTextureFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,
-                OES_TEXTURE_FRAGMENT_SHADER);
 
-        mDrawProgram = assembleProgram(drawVertexShader, drawFragmentShader, mDrawParameters);
         mTextureProgram = assembleProgram(textureVertexShader, textureFragmentShader,
                 mTextureParameters);
-        mOesTextureProgram = assembleProgram(textureVertexShader, oesTextureFragmentShader,
-                mOesTextureParameters);
-        mMeshProgram = assembleProgram(meshVertexShader, textureFragmentShader, mMeshParameters);
         GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);
         checkError();
     }
@@ -348,12 +241,8 @@
         checkError();
         Matrix.setIdentityM(mMatrices, mCurrentMatrixIndex);
         Matrix.orthoM(mProjectionMatrix, 0, 0, width, 0, height, -1, 1);
-        if (getTargetTexture() == null) {
-            mScreenWidth = width;
-            mScreenHeight = height;
-            Matrix.translateM(mMatrices, mCurrentMatrixIndex, 0, height, 0);
-            Matrix.scaleM(mMatrices, mCurrentMatrixIndex, 1, -1, 1);
-        }
+        Matrix.translateM(mMatrices, mCurrentMatrixIndex, 0, height, 0);
+        Matrix.scaleM(mMatrices, mCurrentMatrixIndex, 1, -1, 1);
     }
 
     @Override
@@ -364,34 +253,6 @@
         checkError();
     }
 
-    @Override
-    public void clearBuffer(float[] argb) {
-        GLES20.glClearColor(argb[1], argb[2], argb[3], argb[0]);
-        checkError();
-        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
-        checkError();
-    }
-
-    @Override
-    public float getAlpha() {
-        return mAlphas[mCurrentAlphaIndex];
-    }
-
-    @Override
-    public void setAlpha(float alpha) {
-        mAlphas[mCurrentAlphaIndex] = alpha;
-    }
-
-    @Override
-    public void multiplyAlpha(float alpha) {
-        setAlpha(getAlpha() * alpha);
-    }
-
-    @Override
-    public void translate(float x, float y, float z) {
-        Matrix.translateM(mMatrices, mCurrentMatrixIndex, x, y, z);
-    }
-
     // This is a faster version of translate(x, y, z) because
     // (1) we knows z = 0, (2) we inline the Matrix.translateM call,
     // (3) we unroll the loop
@@ -406,11 +267,6 @@
     }
 
     @Override
-    public void scale(float sx, float sy, float sz) {
-        Matrix.scaleM(mMatrices, mCurrentMatrixIndex, sx, sy, sz);
-    }
-
-    @Override
     public void rotate(float angle, float x, float y, float z) {
         if (angle == 0f) {
             return;
@@ -424,30 +280,7 @@
     }
 
     @Override
-    public void multiplyMatrix(float[] matrix, int offset) {
-        float[] temp = mTempMatrix;
-        float[] currentMatrix = mMatrices;
-        int index = mCurrentMatrixIndex;
-        Matrix.multiplyMM(temp, 0, currentMatrix, index, matrix, offset);
-        System.arraycopy(temp, 0, currentMatrix, index, 16);
-    }
-
-    @Override
-    public void save() {
-        save(SAVE_FLAG_ALL);
-    }
-
-    @Override
     public void save(int saveFlags) {
-        boolean saveAlpha = (saveFlags & SAVE_FLAG_ALPHA) == SAVE_FLAG_ALPHA;
-        if (saveAlpha) {
-            float currentAlpha = getAlpha();
-            mCurrentAlphaIndex++;
-            if (mAlphas.length <= mCurrentAlphaIndex) {
-                mAlphas = Arrays.copyOf(mAlphas, mAlphas.length * 2);
-            }
-            mAlphas[mCurrentAlphaIndex] = currentAlpha;
-        }
         boolean saveMatrix = (saveFlags & SAVE_FLAG_MATRIX) == SAVE_FLAG_MATRIX;
         if (saveMatrix) {
             int currentIndex = mCurrentMatrixIndex;
@@ -463,82 +296,12 @@
     @Override
     public void restore() {
         int restoreFlags = mSaveFlags.removeLast();
-        boolean restoreAlpha = (restoreFlags & SAVE_FLAG_ALPHA) == SAVE_FLAG_ALPHA;
-        if (restoreAlpha) {
-            mCurrentAlphaIndex--;
-        }
         boolean restoreMatrix = (restoreFlags & SAVE_FLAG_MATRIX) == SAVE_FLAG_MATRIX;
         if (restoreMatrix) {
             mCurrentMatrixIndex -= MATRIX_SIZE;
         }
     }
 
-    @Override
-    public void drawLine(float x1, float y1, float x2, float y2, GLPaint paint) {
-        draw(GLES20.GL_LINE_STRIP, OFFSET_DRAW_LINE, COUNT_LINE_VERTEX, x1, y1, x2 - x1, y2 - y1,
-                paint);
-        mCountDrawLine++;
-    }
-
-    @Override
-    public void drawRect(float x, float y, float width, float height, GLPaint paint) {
-        draw(GLES20.GL_LINE_LOOP, OFFSET_DRAW_RECT, COUNT_RECT_VERTEX, x, y, width, height, paint);
-        mCountDrawLine++;
-    }
-
-    private void draw(int type, int offset, int count, float x, float y, float width, float height,
-            GLPaint paint) {
-        draw(type, offset, count, x, y, width, height, paint.getColor(), paint.getLineWidth());
-    }
-
-    private void draw(int type, int offset, int count, float x, float y, float width, float height,
-            int color, float lineWidth) {
-        prepareDraw(offset, color, lineWidth);
-        draw(mDrawParameters, type, count, x, y, width, height);
-    }
-
-    private void prepareDraw(int offset, int color, float lineWidth) {
-        GLES20.glUseProgram(mDrawProgram);
-        checkError();
-        if (lineWidth > 0) {
-            GLES20.glLineWidth(lineWidth);
-            checkError();
-        }
-        float[] colorArray = getColor(color);
-        boolean blendingEnabled = (colorArray[3] < 1f);
-        enableBlending(blendingEnabled);
-        if (blendingEnabled) {
-            GLES20.glBlendColor(colorArray[0], colorArray[1], colorArray[2], colorArray[3]);
-            checkError();
-        }
-
-        GLES20.glUniform4fv(mDrawParameters[INDEX_COLOR].handle, 1, colorArray, 0);
-        setPosition(mDrawParameters, offset);
-        checkError();
-    }
-
-    private float[] getColor(int color) {
-        float alpha = ((color >>> 24) & 0xFF) / 255f * getAlpha();
-        float red = ((color >>> 16) & 0xFF) / 255f * alpha;
-        float green = ((color >>> 8) & 0xFF) / 255f * alpha;
-        float blue = (color & 0xFF) / 255f * alpha;
-        mTempColor[0] = red;
-        mTempColor[1] = green;
-        mTempColor[2] = blue;
-        mTempColor[3] = alpha;
-        return mTempColor;
-    }
-
-    private void enableBlending(boolean enableBlending) {
-        if (enableBlending) {
-            GLES20.glEnable(GLES20.GL_BLEND);
-            checkError();
-        } else {
-            GLES20.glDisable(GLES20.GL_BLEND);
-            checkError();
-        }
-    }
-
     private void setPosition(ShaderParameter[] params, int offset) {
         GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mBoxCoordinates);
         checkError();
@@ -570,13 +333,6 @@
     }
 
     @Override
-    public void fillRect(float x, float y, float width, float height, int color) {
-        draw(GLES20.GL_TRIANGLE_STRIP, OFFSET_FILL_RECT, COUNT_FILL_VERTEX, x, y, width, height,
-                color, 0f);
-        mCountFillRect++;
-    }
-
-    @Override
     public void drawTexture(BasicTexture texture, int x, int y, int width, int height) {
         if (width <= 0 || height <= 0) {
             return;
@@ -588,17 +344,7 @@
     }
 
     private static void copyTextureCoordinates(BasicTexture texture, RectF outRect) {
-        int left = 0;
-        int top = 0;
-        int right = texture.getWidth();
-        int bottom = texture.getHeight();
-        if (texture.hasBorder()) {
-            left = 1;
-            top = 1;
-            right -= 1;
-            bottom -= 1;
-        }
-        outRect.set(left, top, right, bottom);
+        outRect.set(0, 0, texture.getWidth(), texture.getHeight());
     }
 
     @Override
@@ -613,16 +359,6 @@
         drawTextureRect(texture, mTempSourceRect, mTempTargetRect);
     }
 
-    @Override
-    public void drawTexture(BasicTexture texture, float[] textureTransform, int x, int y, int w,
-            int h) {
-        if (w <= 0 || h <= 0) {
-            return;
-        }
-        mTempTargetRect.set(x, y, x + w, y + h);
-        drawTextureRect(texture, textureTransform, mTempTargetRect);
-    }
-
     private void drawTextureRect(BasicTexture texture, RectF source, RectF target) {
         setTextureMatrix(source);
         drawTextureRect(texture, mTempTextureMatrix, target);
@@ -667,30 +403,15 @@
         setPosition(params, OFFSET_FILL_RECT);
         GLES20.glUniformMatrix4fv(params[INDEX_TEXTURE_MATRIX].handle, 1, false, textureMatrix, 0);
         checkError();
-        if (texture.isFlippedVertically()) {
-            save(SAVE_FLAG_MATRIX);
-            translate(0, target.centerY());
-            scale(1, -1, 1);
-            translate(0, -target.centerY());
-        }
         draw(params, GLES20.GL_TRIANGLE_STRIP, COUNT_FILL_VERTEX, target.left, target.top,
                 target.width(), target.height());
-        if (texture.isFlippedVertically()) {
-            restore();
-        }
-        mCountTextureRect++;
     }
 
     private ShaderParameter[] prepareTexture(BasicTexture texture) {
         ShaderParameter[] params;
         int program;
-        if (texture.getTarget() == GLES20.GL_TEXTURE_2D) {
-            params = mTextureParameters;
-            program = mTextureProgram;
-        } else {
-            params = mOesTextureParameters;
-            program = mOesTextureProgram;
-        }
+        params = mTextureParameters;
+        program = mTextureProgram;
         prepareTexture(texture, program, params);
         return params;
     }
@@ -699,89 +420,20 @@
         deleteRecycledResources();
         GLES20.glUseProgram(program);
         checkError();
-        enableBlending(!texture.isOpaque() || getAlpha() < OPAQUE_ALPHA);
+        GLES20.glDisable(GLES20.GL_BLEND);
+        checkError();
         GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
         checkError();
         texture.onBind(this);
-        GLES20.glBindTexture(texture.getTarget(), texture.getId());
+        GLES20.glBindTexture(GL_TARGET, texture.getId());
         checkError();
         GLES20.glUniform1i(params[INDEX_TEXTURE_SAMPLER].handle, 0);
         checkError();
-        GLES20.glUniform1f(params[INDEX_ALPHA].handle, getAlpha());
+        GLES20.glUniform1f(params[INDEX_ALPHA].handle, 1);
         checkError();
     }
 
     @Override
-    public void drawMesh(BasicTexture texture, int x, int y, int xyBuffer, int uvBuffer,
-            int indexBuffer, int indexCount) {
-        prepareTexture(texture, mMeshProgram, mMeshParameters);
-
-        GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
-        checkError();
-
-        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, xyBuffer);
-        checkError();
-        int positionHandle = mMeshParameters[INDEX_POSITION].handle;
-        GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false,
-                VERTEX_STRIDE, 0);
-        checkError();
-
-        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, uvBuffer);
-        checkError();
-        int texCoordHandle = mMeshParameters[INDEX_TEXTURE_COORD].handle;
-        GLES20.glVertexAttribPointer(texCoordHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT,
-                false, VERTEX_STRIDE, 0);
-        checkError();
-        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
-        checkError();
-
-        GLES20.glEnableVertexAttribArray(positionHandle);
-        checkError();
-        GLES20.glEnableVertexAttribArray(texCoordHandle);
-        checkError();
-
-        setMatrix(mMeshParameters, x, y, 1, 1);
-        GLES20.glDrawElements(GLES20.GL_TRIANGLE_STRIP, indexCount, GLES20.GL_UNSIGNED_BYTE, 0);
-        checkError();
-
-        GLES20.glDisableVertexAttribArray(positionHandle);
-        checkError();
-        GLES20.glDisableVertexAttribArray(texCoordHandle);
-        checkError();
-        GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0);
-        checkError();
-        mCountDrawMesh++;
-    }
-
-    @Override
-    public void drawMixed(BasicTexture texture, int toColor, float ratio, int x, int y, int w, int h) {
-        copyTextureCoordinates(texture, mTempSourceRect);
-        mTempTargetRect.set(x, y, x + w, y + h);
-        drawMixed(texture, toColor, ratio, mTempSourceRect, mTempTargetRect);
-    }
-
-    @Override
-    public void drawMixed(BasicTexture texture, int toColor, float ratio, RectF source, RectF target) {
-        if (target.width() <= 0 || target.height() <= 0) {
-            return;
-        }
-        save(SAVE_FLAG_ALPHA);
-
-        float currentAlpha = getAlpha();
-        float cappedRatio = Math.min(1f, Math.max(0f, ratio));
-
-        float textureAlpha = (1f - cappedRatio) * currentAlpha;
-        setAlpha(textureAlpha);
-        drawTexture(texture, source, target);
-
-        float colorAlpha = cappedRatio * currentAlpha;
-        setAlpha(colorAlpha);
-        fillRect(target.left, target.top, target.width(), target.height(), toColor);
-
-        restore();
-    }
-
-    @Override
     public boolean unloadTexture(BasicTexture texture) {
         boolean unload = texture.isLoaded();
         if (unload) {
@@ -793,13 +445,6 @@
     }
 
     @Override
-    public void deleteBuffer(int bufferId) {
-        synchronized (mUnboundTextures) {
-            mDeleteBuffers.add(bufferId);
-        }
-    }
-
-    @Override
     public void deleteRecycledResources() {
         synchronized (mUnboundTextures) {
             IntArray ids = mUnboundTextures;
@@ -807,134 +452,41 @@
                 mGLId.glDeleteTextures(null, ids.size(), ids.getInternalArray(), 0);
                 ids.clear();
             }
-
-            ids = mDeleteBuffers;
-            if (ids.size() > 0) {
-                mGLId.glDeleteBuffers(null, ids.size(), ids.getInternalArray(), 0);
-                ids.clear();
-            }
-        }
-    }
-
-    @Override
-    public void dumpStatisticsAndClear() {
-        String line = String.format("MESH:%d, TEX_RECT:%d, FILL_RECT:%d, LINE:%d", mCountDrawMesh,
-                mCountTextureRect, mCountFillRect, mCountDrawLine);
-        mCountDrawMesh = 0;
-        mCountTextureRect = 0;
-        mCountFillRect = 0;
-        mCountDrawLine = 0;
-        Log.d(TAG, line);
-    }
-
-    @Override
-    public void endRenderTarget() {
-        RawTexture oldTexture = mTargetTextures.remove(mTargetTextures.size() - 1);
-        RawTexture texture = getTargetTexture();
-        setRenderTarget(oldTexture, texture);
-        restore(); // restore matrix and alpha
-    }
-
-    @Override
-    public void beginRenderTarget(RawTexture texture) {
-        save(); // save matrix and alpha and blending
-        RawTexture oldTexture = getTargetTexture();
-        mTargetTextures.add(texture);
-        setRenderTarget(oldTexture, texture);
-    }
-
-    private RawTexture getTargetTexture() {
-        return mTargetTextures.get(mTargetTextures.size() - 1);
-    }
-
-    private void setRenderTarget(BasicTexture oldTexture, RawTexture texture) {
-        if (oldTexture == null && texture != null) {
-            GLES20.glGenFramebuffers(1, mFrameBuffer, 0);
-            checkError();
-            GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffer[0]);
-            checkError();
-        } else if (oldTexture != null && texture == null) {
-            GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
-            checkError();
-            GLES20.glDeleteFramebuffers(1, mFrameBuffer, 0);
-            checkError();
-        }
-
-        if (texture == null) {
-            setSize(mScreenWidth, mScreenHeight);
-        } else {
-            setSize(texture.getWidth(), texture.getHeight());
-
-            if (!texture.isLoaded()) {
-                texture.prepare(this);
-            }
-
-            GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
-                    texture.getTarget(), texture.getId(), 0);
-            checkError();
-
-            checkFramebufferStatus();
-        }
-    }
-
-    private static void checkFramebufferStatus() {
-        int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);
-        if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) {
-            String msg = "";
-            switch (status) {
-                case GLES20.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
-                    msg = "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT";
-                    break;
-                case GLES20.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
-                    msg = "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS";
-                    break;
-                case GLES20.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
-                    msg = "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT";
-                    break;
-                case GLES20.GL_FRAMEBUFFER_UNSUPPORTED:
-                    msg = "GL_FRAMEBUFFER_UNSUPPORTED";
-                    break;
-            }
-            throw new RuntimeException(msg + ":" + Integer.toHexString(status));
         }
     }
 
     @Override
     public void setTextureParameters(BasicTexture texture) {
-        int target = texture.getTarget();
-        GLES20.glBindTexture(target, texture.getId());
+        GLES20.glBindTexture(GL_TARGET, texture.getId());
         checkError();
-        GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
-        GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
-        GLES20.glTexParameterf(target, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
-        GLES20.glTexParameterf(target, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
+        GLES20.glTexParameteri(GL_TARGET, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
+        GLES20.glTexParameteri(GL_TARGET, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
+        GLES20.glTexParameterf(GL_TARGET, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
+        GLES20.glTexParameterf(GL_TARGET, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
     }
 
     @Override
     public void initializeTextureSize(BasicTexture texture, int format, int type) {
-        int target = texture.getTarget();
-        GLES20.glBindTexture(target, texture.getId());
+        GLES20.glBindTexture(GL_TARGET, texture.getId());
         checkError();
         int width = texture.getTextureWidth();
         int height = texture.getTextureHeight();
-        GLES20.glTexImage2D(target, 0, format, width, height, 0, format, type, null);
+        GLES20.glTexImage2D(GL_TARGET, 0, format, width, height, 0, format, type, null);
     }
 
     @Override
     public void initializeTexture(BasicTexture texture, Bitmap bitmap) {
-        int target = texture.getTarget();
-        GLES20.glBindTexture(target, texture.getId());
+        GLES20.glBindTexture(GL_TARGET, texture.getId());
         checkError();
-        GLUtils.texImage2D(target, 0, bitmap, 0);
+        GLUtils.texImage2D(GL_TARGET, 0, bitmap, 0);
     }
 
     @Override
     public void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap,
             int format, int type) {
-        int target = texture.getTarget();
-        GLES20.glBindTexture(target, texture.getId());
+        GLES20.glBindTexture(GL_TARGET, texture.getId());
         checkError();
-        GLUtils.texSubImage2D(target, 0, xOffset, yOffset, bitmap, format, type);
+        GLUtils.texSubImage2D(GL_TARGET, 0, xOffset, yOffset, bitmap, format, type);
     }
 
     @Override
@@ -942,11 +494,6 @@
         return uploadBuffer(buf, FLOAT_SIZE);
     }
 
-    @Override
-    public int uploadBuffer(ByteBuffer buf) {
-        return uploadBuffer(buf, 1);
-    }
-
     private int uploadBuffer(Buffer buffer, int elementSize) {
         mGLId.glGenBuffers(1, mTempIntArray, 0);
         checkError();
@@ -967,40 +514,6 @@
         }
     }
 
-    @SuppressWarnings("unused")
-    private static void printMatrix(String message, float[] m, int offset) {
-        StringBuilder b = new StringBuilder(message);
-        for (int i = 0; i < MATRIX_SIZE; i++) {
-            b.append(' ');
-            if (i % 4 == 0) {
-                b.append('\n');
-            }
-            b.append(m[offset + i]);
-        }
-        Log.v(TAG, b.toString());
-    }
-
-    @Override
-    public void recoverFromLightCycle() {
-        GLES20.glViewport(0, 0, mWidth, mHeight);
-        GLES20.glDisable(GLES20.GL_DEPTH_TEST);
-        GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);
-        checkError();
-    }
-
-    @Override
-    public void getBounds(Rect bounds, int x, int y, int width, int height) {
-        Matrix.translateM(mTempMatrix, 0, mMatrices, mCurrentMatrixIndex, x, y, 0f);
-        Matrix.scaleM(mTempMatrix, 0, width, height, 1f);
-        Matrix.multiplyMV(mTempMatrix, MATRIX_SIZE, mTempMatrix, 0, BOUNDS_COORDINATES, 0);
-        Matrix.multiplyMV(mTempMatrix, MATRIX_SIZE + 4, mTempMatrix, 0, BOUNDS_COORDINATES, 4);
-        bounds.left = Math.round(mTempMatrix[MATRIX_SIZE]);
-        bounds.right = Math.round(mTempMatrix[MATRIX_SIZE + 4]);
-        bounds.top = Math.round(mTempMatrix[MATRIX_SIZE + 1]);
-        bounds.bottom = Math.round(mTempMatrix[MATRIX_SIZE + 5]);
-        bounds.sort();
-    }
-
     @Override
     public GLId getGLId() {
         return mGLId;
diff --git a/WallpaperPicker/src/com/android/gallery3d/glrenderer/GLPaint.java b/WallpaperPicker/src/com/android/gallery3d/glrenderer/GLPaint.java
deleted file mode 100644
index b26e9ab..0000000
--- a/WallpaperPicker/src/com/android/gallery3d/glrenderer/GLPaint.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2010 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.gallery3d.glrenderer;
-
-import com.android.gallery3d.common.Utils;
-
-public class GLPaint {
-    private float mLineWidth = 1f;
-    private int mColor = 0;
-
-    public void setColor(int color) {
-        mColor = color;
-    }
-
-    public int getColor() {
-        return mColor;
-    }
-
-    public void setLineWidth(float width) {
-        Utils.assertTrue(width >= 0);
-        mLineWidth = width;
-    }
-
-    public float getLineWidth() {
-        return mLineWidth;
-    }
-}
diff --git a/WallpaperPicker/src/com/android/gallery3d/glrenderer/RawTexture.java b/WallpaperPicker/src/com/android/gallery3d/glrenderer/RawTexture.java
deleted file mode 100644
index 93f0fdf..0000000
--- a/WallpaperPicker/src/com/android/gallery3d/glrenderer/RawTexture.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2010 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.gallery3d.glrenderer;
-
-import android.util.Log;
-
-import javax.microedition.khronos.opengles.GL11;
-
-public class RawTexture extends BasicTexture {
-    private static final String TAG = "RawTexture";
-
-    private final boolean mOpaque;
-    private boolean mIsFlipped;
-
-    public RawTexture(int width, int height, boolean opaque) {
-        mOpaque = opaque;
-        setSize(width, height);
-    }
-
-    @Override
-    public boolean isOpaque() {
-        return mOpaque;
-    }
-
-    @Override
-    public boolean isFlippedVertically() {
-        return mIsFlipped;
-    }
-
-    public void setIsFlippedVertically(boolean isFlipped) {
-        mIsFlipped = isFlipped;
-    }
-
-    protected void prepare(GLCanvas canvas) {
-        GLId glId = canvas.getGLId();
-        mId = glId.generateTexture();
-        canvas.initializeTextureSize(this, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE);
-        canvas.setTextureParameters(this);
-        mState = STATE_LOADED;
-        setAssociatedCanvas(canvas);
-    }
-
-    @Override
-    protected boolean onBind(GLCanvas canvas) {
-        if (isLoaded()) return true;
-        Log.w(TAG, "lost the content due to context change");
-        return false;
-    }
-
-    @Override
-     public void yield() {
-         // we cannot free the texture because we have no backup.
-     }
-
-    @Override
-    protected int getTarget() {
-        return GL11.GL_TEXTURE_2D;
-    }
-}
diff --git a/WallpaperPicker/src/com/android/gallery3d/glrenderer/Texture.java b/WallpaperPicker/src/com/android/gallery3d/glrenderer/Texture.java
index 3dcae4a..e71a379 100644
--- a/WallpaperPicker/src/com/android/gallery3d/glrenderer/Texture.java
+++ b/WallpaperPicker/src/com/android/gallery3d/glrenderer/Texture.java
@@ -24,21 +24,14 @@
 // This is the current texture hierarchy:
 //
 // Texture
-// -- ColorTexture
-// -- FadeInTexture
 // -- BasicTexture
 //    -- UploadedTexture
 //       -- BitmapTexture
 //       -- Tile
-//       -- ResourceTexture
-//          -- NinePatchTexture
-//       -- CanvasTexture
-//          -- StringTexture
 //
 public interface Texture {
     public int getWidth();
     public int getHeight();
     public void draw(GLCanvas canvas, int x, int y);
     public void draw(GLCanvas canvas, int x, int y, int w, int h);
-    public boolean isOpaque();
 }
diff --git a/WallpaperPicker/src/com/android/gallery3d/glrenderer/UploadedTexture.java b/WallpaperPicker/src/com/android/gallery3d/glrenderer/UploadedTexture.java
index 8075bf8..607e2a9 100644
--- a/WallpaperPicker/src/com/android/gallery3d/glrenderer/UploadedTexture.java
+++ b/WallpaperPicker/src/com/android/gallery3d/glrenderer/UploadedTexture.java
@@ -19,14 +19,12 @@
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
 import android.opengl.GLUtils;
+import android.util.Pair;
 
 import com.android.gallery3d.common.Utils;
-import com.android.launcher3.util.Thunk;
 
 import java.util.HashMap;
 
-import javax.microedition.khronos.opengles.GL11;
-
 // UploadedTextures use a Bitmap for the content of the texture.
 //
 // Subclasses should implement onGetBitmap() to provide the Bitmap and
@@ -45,89 +43,29 @@
 
     // To prevent keeping allocation the borders, we store those used borders here.
     // Since the length will be power of two, it won't use too much memory.
-    private static HashMap<BorderKey, Bitmap> sBorderLines =
-            new HashMap<BorderKey, Bitmap>();
-    private static BorderKey sBorderKey = new BorderKey();
+    private static HashMap<BorderKey, Bitmap> sBorderLines = new HashMap<BorderKey, Bitmap>();
 
-    @SuppressWarnings("unused")
-    private static final String TAG = "Texture";
+    private static class BorderKey extends Pair<Config, Integer> {
+        public BorderKey(Config config, boolean vertical, int length) {
+            super(config, vertical ? length : -length);
+        }
+    }
+
     private boolean mContentValid = true;
-
-    // indicate this textures is being uploaded in background
-    private boolean mIsUploading = false;
-    private boolean mOpaque = true;
-    private boolean mThrottled = false;
-    private static int sUploadedCount;
-    private static final int UPLOAD_LIMIT = 100;
-
     protected Bitmap mBitmap;
-    private int mBorder;
 
     protected UploadedTexture() {
-        this(false);
-    }
-
-    protected UploadedTexture(boolean hasBorder) {
         super(null, 0, STATE_UNLOADED);
-        if (hasBorder) {
-            setBorder(true);
-            mBorder = 1;
-        }
     }
 
-    protected void setIsUploading(boolean uploading) {
-        mIsUploading = uploading;
-    }
-
-    public boolean isUploading() {
-        return mIsUploading;
-    }
-
-    @Thunk static class BorderKey implements Cloneable {
-        public boolean vertical;
-        public Config config;
-        public int length;
-
-        @Override
-        public int hashCode() {
-            int x = config.hashCode() ^ length;
-            return vertical ? x : -x;
-        }
-
-        @Override
-        public boolean equals(Object object) {
-            if (!(object instanceof BorderKey)) return false;
-            BorderKey o = (BorderKey) object;
-            return vertical == o.vertical
-                    && config == o.config && length == o.length;
-        }
-
-        @Override
-        public BorderKey clone() {
-            try {
-                return (BorderKey) super.clone();
-            } catch (CloneNotSupportedException e) {
-                throw new AssertionError(e);
-            }
-        }
-    }
-
-    protected void setThrottled(boolean throttled) {
-        mThrottled = throttled;
-    }
-
-    private static Bitmap getBorderLine(
-            boolean vertical, Config config, int length) {
-        BorderKey key = sBorderKey;
-        key.vertical = vertical;
-        key.config = config;
-        key.length = length;
+    private static Bitmap getBorderLine(boolean vertical, Config config, int length) {
+        BorderKey key = new BorderKey(config, vertical, length);
         Bitmap bitmap = sBorderLines.get(key);
         if (bitmap == null) {
             bitmap = vertical
                     ? Bitmap.createBitmap(1, length, config)
                     : Bitmap.createBitmap(length, 1, config);
-            sBorderLines.put(key.clone(), bitmap);
+            sBorderLines.put(key, bitmap);
         }
         return bitmap;
     }
@@ -135,8 +73,8 @@
     private Bitmap getBitmap() {
         if (mBitmap == null) {
             mBitmap = onGetBitmap();
-            int w = mBitmap.getWidth() + mBorder * 2;
-            int h = mBitmap.getHeight() + mBorder * 2;
+            int w = mBitmap.getWidth();
+            int h = mBitmap.getHeight();
             if (mWidth == UNSPECIFIED) {
                 setSize(w, h);
             }
@@ -186,37 +124,23 @@
      */
     public void updateContent(GLCanvas canvas) {
         if (!isLoaded()) {
-            if (mThrottled && ++sUploadedCount > UPLOAD_LIMIT) {
-                return;
-            }
             uploadToCanvas(canvas);
         } else if (!mContentValid) {
             Bitmap bitmap = getBitmap();
             int format = GLUtils.getInternalFormat(bitmap);
             int type = GLUtils.getType(bitmap);
-            canvas.texSubImage2D(this, mBorder, mBorder, bitmap, format, type);
+            canvas.texSubImage2D(this, 0, 0, bitmap, format, type);
             freeBitmap();
             mContentValid = true;
         }
     }
 
-    public static void resetUploadLimit() {
-        sUploadedCount = 0;
-    }
-
-    public static boolean uploadLimitReached() {
-        return sUploadedCount > UPLOAD_LIMIT;
-    }
-
     private void uploadToCanvas(GLCanvas canvas) {
-
         Bitmap bitmap = getBitmap();
         if (bitmap != null) {
             try {
                 int bWidth = bitmap.getWidth();
                 int bHeight = bitmap.getHeight();
-                int width = bWidth + mBorder * 2;
-                int height = bHeight + mBorder * 2;
                 int texWidth = getTextureWidth();
                 int texHeight = getTextureHeight();
 
@@ -234,28 +158,18 @@
                     Config config = bitmap.getConfig();
 
                     canvas.initializeTextureSize(this, format, type);
-                    canvas.texSubImage2D(this, mBorder, mBorder, bitmap, format, type);
-
-                    if (mBorder > 0) {
-                        // Left border
-                        Bitmap line = getBorderLine(true, config, texHeight);
-                        canvas.texSubImage2D(this, 0, 0, line, format, type);
-
-                        // Top border
-                        line = getBorderLine(false, config, texWidth);
-                        canvas.texSubImage2D(this, 0, 0, line, format, type);
-                    }
+                    canvas.texSubImage2D(this, 0, 0, bitmap, format, type);
 
                     // Right border
-                    if (mBorder + bWidth < texWidth) {
+                    if (bWidth < texWidth) {
                         Bitmap line = getBorderLine(true, config, texHeight);
-                        canvas.texSubImage2D(this, mBorder + bWidth, 0, line, format, type);
+                        canvas.texSubImage2D(this, bWidth, 0, line, format, type);
                     }
 
                     // Bottom border
-                    if (mBorder + bHeight < texHeight) {
+                    if (bHeight < texHeight) {
                         Bitmap line = getBorderLine(false, config, texWidth);
-                        canvas.texSubImage2D(this, 0, mBorder + bHeight, line, format, type);
+                        canvas.texSubImage2D(this, 0, bHeight, line, format, type);
                     }
                 }
             } finally {
@@ -278,20 +192,6 @@
     }
 
     @Override
-    protected int getTarget() {
-        return GL11.GL_TEXTURE_2D;
-    }
-
-    public void setOpaque(boolean isOpaque) {
-        mOpaque = isOpaque;
-    }
-
-    @Override
-    public boolean isOpaque() {
-        return mOpaque;
-    }
-
-    @Override
     public void recycle() {
         super.recycle();
         if (mBitmap != null) freeBitmap();
diff --git a/WallpaperPicker/src/com/android/launcher3/CropView.java b/WallpaperPicker/src/com/android/launcher3/CropView.java
index 50f779a..4770a71 100644
--- a/WallpaperPicker/src/com/android/launcher3/CropView.java
+++ b/WallpaperPicker/src/com/android/launcher3/CropView.java
@@ -189,6 +189,17 @@
     public void onScaleEnd(ScaleGestureDetector detector) {
     }
 
+    /**
+     * Offsets wallpaper preview according to the state it will be displayed in upon returning home.
+     * @param offset Ranges from 0 to 1, where 0 is the leftmost parallax and 1 is the rightmost.
+     */
+    public void setParallaxOffset(float offset, RectF crop) {
+        offset = Math.max(0, Math.min(offset, 1)); // Make sure the offset is in the correct range.
+        float screenWidth = getWidth() / mRenderer.scale;
+        mCenterX = screenWidth / 2 + offset * (crop.width() - screenWidth) + crop.left;
+        updateCenter();
+    }
+
     public void moveToLeft() {
         if (getWidth() == 0 || getHeight() == 0) {
             final ViewTreeObserver observer = getViewTreeObserver();
diff --git a/WallpaperPicker/src/com/android/launcher3/LiveWallpaperListAdapter.java b/WallpaperPicker/src/com/android/launcher3/LiveWallpaperListAdapter.java
deleted file mode 100644
index b53fce1..0000000
--- a/WallpaperPicker/src/com/android/launcher3/LiveWallpaperListAdapter.java
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import android.app.WallpaperInfo;
-import android.app.WallpaperManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.graphics.drawable.Drawable;
-import android.os.AsyncTask;
-import android.service.wallpaper.WallpaperService;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.ImageView;
-import android.widget.ListAdapter;
-import android.widget.TextView;
-
-import com.android.launcher3.util.Thunk;
-
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.text.Collator;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-public class LiveWallpaperListAdapter extends BaseAdapter implements ListAdapter {
-    private static final String LOG_TAG = "LiveWallpaperListAdapter";
-
-    private final LayoutInflater mInflater;
-    private final PackageManager mPackageManager;
-
-    @Thunk List<LiveWallpaperTile> mWallpapers;
-
-    @SuppressWarnings("unchecked")
-    public LiveWallpaperListAdapter(Context context) {
-        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-        mPackageManager = context.getPackageManager();
-
-        List<ResolveInfo> list = mPackageManager.queryIntentServices(
-                new Intent(WallpaperService.SERVICE_INTERFACE),
-                PackageManager.GET_META_DATA);
-
-        mWallpapers = new ArrayList<LiveWallpaperTile>();
-
-        new LiveWallpaperEnumerator(context).execute(list);
-    }
-
-    public int getCount() {
-        if (mWallpapers == null) {
-            return 0;
-        }
-        return mWallpapers.size();
-    }
-
-    public LiveWallpaperTile getItem(int position) {
-        return mWallpapers.get(position);
-    }
-
-    public long getItemId(int position) {
-        return position;
-    }
-
-    public View getView(int position, View convertView, ViewGroup parent) {
-        View view;
-
-        if (convertView == null) {
-            view = mInflater.inflate(R.layout.wallpaper_picker_live_wallpaper_item, parent, false);
-        } else {
-            view = convertView;
-        }
-
-        LiveWallpaperTile wallpaperInfo = mWallpapers.get(position);
-        wallpaperInfo.setView(view);
-        ImageView image = (ImageView) view.findViewById(R.id.wallpaper_image);
-        ImageView icon = (ImageView) view.findViewById(R.id.wallpaper_icon);
-        if (wallpaperInfo.mThumbnail != null) {
-            image.setImageDrawable(wallpaperInfo.mThumbnail);
-            icon.setVisibility(View.GONE);
-        } else {
-            icon.setImageDrawable(wallpaperInfo.mInfo.loadIcon(mPackageManager));
-            icon.setVisibility(View.VISIBLE);
-        }
-
-        TextView label = (TextView) view.findViewById(R.id.wallpaper_item_label);
-        label.setText(wallpaperInfo.mInfo.loadLabel(mPackageManager));
-
-        return view;
-    }
-
-    public static class LiveWallpaperTile extends WallpaperPickerActivity.WallpaperTileInfo {
-        @Thunk Drawable mThumbnail;
-        @Thunk WallpaperInfo mInfo;
-        public LiveWallpaperTile(Drawable thumbnail, WallpaperInfo info, Intent intent) {
-            mThumbnail = thumbnail;
-            mInfo = info;
-        }
-        @Override
-        public void onClick(WallpaperPickerActivity a) {
-            Intent preview = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);
-            preview.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT,
-                    mInfo.getComponent());
-            a.startActivityForResultSafely(preview,
-                    WallpaperPickerActivity.PICK_WALLPAPER_THIRD_PARTY_ACTIVITY);
-        }
-    }
-
-    private class LiveWallpaperEnumerator extends
-            AsyncTask<List<ResolveInfo>, LiveWallpaperTile, Void> {
-        private Context mContext;
-        private int mWallpaperPosition;
-
-        public LiveWallpaperEnumerator(Context context) {
-            super();
-            mContext = context;
-            mWallpaperPosition = 0;
-        }
-
-        @Override
-        protected Void doInBackground(List<ResolveInfo>... params) {
-            final PackageManager packageManager = mContext.getPackageManager();
-
-            List<ResolveInfo> list = params[0];
-
-            Collections.sort(list, new Comparator<ResolveInfo>() {
-                final Collator mCollator;
-
-                {
-                    mCollator = Collator.getInstance();
-                }
-
-                public int compare(ResolveInfo info1, ResolveInfo info2) {
-                    return mCollator.compare(info1.loadLabel(packageManager),
-                            info2.loadLabel(packageManager));
-                }
-            });
-
-            for (ResolveInfo resolveInfo : list) {
-                WallpaperInfo info = null;
-                try {
-                    info = new WallpaperInfo(mContext, resolveInfo);
-                } catch (XmlPullParserException e) {
-                    Log.w(LOG_TAG, "Skipping wallpaper " + resolveInfo.serviceInfo, e);
-                    continue;
-                } catch (IOException e) {
-                    Log.w(LOG_TAG, "Skipping wallpaper " + resolveInfo.serviceInfo, e);
-                    continue;
-                }
-
-
-                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);
-                publishProgress(wallpaper);
-            }
-            // Send a null object to show loading is finished
-            publishProgress((LiveWallpaperTile) null);
-
-            return null;
-        }
-
-        @Override
-        protected void onProgressUpdate(LiveWallpaperTile...infos) {
-            for (LiveWallpaperTile info : infos) {
-                if (info == null) {
-                    LiveWallpaperListAdapter.this.notifyDataSetChanged();
-                    break;
-                }
-                if (info.mThumbnail != null) {
-                    info.mThumbnail.setDither(true);
-                }
-                if (mWallpaperPosition < mWallpapers.size()) {
-                    mWallpapers.set(mWallpaperPosition, info);
-                } else {
-                    mWallpapers.add(info);
-                }
-                mWallpaperPosition++;
-            }
-        }
-    }
-}
diff --git a/WallpaperPicker/src/com/android/launcher3/SavedWallpaperImages.java b/WallpaperPicker/src/com/android/launcher3/SavedWallpaperImages.java
index 64b0ac4..9124e41 100644
--- a/WallpaperPicker/src/com/android/launcher3/SavedWallpaperImages.java
+++ b/WallpaperPicker/src/com/android/launcher3/SavedWallpaperImages.java
@@ -26,29 +26,24 @@
 import android.graphics.drawable.BitmapDrawable;
 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.launcher3.wallpapertileinfo.FileWallpaperInfo;
 
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.List;
 
+public class SavedWallpaperImages {
 
-public class SavedWallpaperImages extends BaseAdapter implements ListAdapter {
     private static String TAG = "Launcher3.SavedWallpaperImages";
-    private ImageDb mDb;
-    ArrayList<SavedWallpaperTile> mImages;
-    Context mContext;
-    LayoutInflater mLayoutInflater;
 
-    public static class SavedWallpaperTile extends WallpaperPickerActivity.FileWallpaperInfo {
+    public static class SavedWallpaperInfo extends FileWallpaperInfo {
+
         private int mDbId;
-        public SavedWallpaperTile(int dbId, File target, Drawable thumb) {
+
+        public SavedWallpaperInfo(int dbId, File target, Drawable thumb) {
             super(target, thumb);
             mDbId = dbId;
         }
@@ -59,19 +54,22 @@
         }
     }
 
+    private final ImageDb mDb;
+    private final Context mContext;
+
     public SavedWallpaperImages(Context context) {
         // We used to store the saved images in the cache directory, but that meant they'd get
         // deleted sometimes-- move them to the data directory
         ImageDb.moveFromCacheDirectoryIfNecessary(context);
         mDb = new ImageDb(context);
         mContext = context;
-        mLayoutInflater = LayoutInflater.from(context);
     }
 
-    public void loadThumbnailsAndImageIdList() {
-        mImages = new ArrayList<SavedWallpaperTile>();
+    public List<SavedWallpaperInfo> loadThumbnailsAndImageIdList() {
+        List<SavedWallpaperInfo> result = new ArrayList<SavedWallpaperInfo>();
+
         SQLiteDatabase db = mDb.getReadableDatabase();
-        Cursor result = db.query(ImageDb.TABLE_NAME,
+        Cursor c = db.query(ImageDb.TABLE_NAME,
                 new String[] { ImageDb.COLUMN_ID,
                     ImageDb.COLUMN_IMAGE_THUMBNAIL_FILENAME,
                     ImageDb.COLUMN_IMAGE_FILENAME}, // cols to return
@@ -82,43 +80,24 @@
                 ImageDb.COLUMN_ID + " DESC",
                 null);
 
-        while (result.moveToNext()) {
-            String filename = result.getString(1);
+        while (c.moveToNext()) {
+            String filename = c.getString(1);
             File file = new File(mContext.getFilesDir(), filename);
 
             Bitmap thumb = BitmapFactory.decodeFile(file.getAbsolutePath());
             if (thumb != null) {
-                mImages.add(new SavedWallpaperTile(result.getInt(0),
-                        new File(mContext.getFilesDir(), result.getString(2)),
-                        new BitmapDrawable(thumb)));
+                result.add(new SavedWallpaperInfo(c.getInt(0),
+                        new File(mContext.getFilesDir(), c.getString(2)),
+                        new BitmapDrawable(mContext.getResources(), thumb)));
             }
         }
-        result.close();
+        c.close();
+        return result;
     }
 
-    public int getCount() {
-        return mImages.size();
-    }
+    public void deleteImage(int id) {
+        SQLiteDatabase db = mDb.getWritableDatabase();
 
-    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, convertView, parent, thumbDrawable);
-    }
-
-    private Pair<String, String> getImageFilenames(int id) {
-        SQLiteDatabase db = mDb.getReadableDatabase();
         Cursor result = db.query(ImageDb.TABLE_NAME,
                 new String[] { ImageDb.COLUMN_IMAGE_THUMBNAIL_FILENAME,
                     ImageDb.COLUMN_IMAGE_FILENAME }, // cols to return
@@ -128,24 +107,12 @@
                 null,
                 null,
                 null);
-        if (result.getCount() > 0) {
-            result.moveToFirst();
-            String thumbFilename = result.getString(0);
-            String imageFilename = result.getString(1);
-            result.close();
-            return new Pair<String, String>(thumbFilename, imageFilename);
-        } else {
-            return null;
+        if (result.moveToFirst()) {
+            new File(mContext.getFilesDir(), result.getString(0)).delete();
+            new File(mContext.getFilesDir(), result.getString(1)).delete();
         }
-    }
+        result.close();
 
-    public void deleteImage(int id) {
-        Pair<String, String> filenames = getImageFilenames(id);
-        File imageFile = new File(mContext.getFilesDir(), filenames.first);
-        imageFile.delete();
-        File thumbFile = new File(mContext.getFilesDir(), filenames.second);
-        thumbFile.delete();
-        SQLiteDatabase db = mDb.getWritableDatabase();
         db.delete(ImageDb.TABLE_NAME,
                 ImageDb.COLUMN_ID + " = ?", // SELECT query
                 new String[] {
@@ -177,20 +144,16 @@
         }
     }
 
-    static class ImageDb extends SQLiteOpenHelper {
+    private static class ImageDb extends SQLiteOpenHelper {
         final static int DB_VERSION = 1;
         final static String TABLE_NAME = "saved_wallpaper_images";
         final static String COLUMN_ID = "id";
         final static String COLUMN_IMAGE_THUMBNAIL_FILENAME = "image_thumbnail";
         final static String COLUMN_IMAGE_FILENAME = "image";
 
-        Context mContext;
-
         public ImageDb(Context context) {
             super(context, context.getDatabasePath(LauncherFiles.WALLPAPER_IMAGES_DB).getPath(),
                     null, DB_VERSION);
-            // Store the context for later use
-            mContext = context;
         }
 
         public static void moveFromCacheDirectoryIfNecessary(Context context) {
diff --git a/WallpaperPicker/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java b/WallpaperPicker/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java
deleted file mode 100644
index f46da53..0000000
--- a/WallpaperPicker/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.ListAdapter;
-import android.widget.TextView;
-
-import com.android.launcher3.util.Thunk;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class ThirdPartyWallpaperPickerListAdapter extends BaseAdapter implements ListAdapter {
-    private final LayoutInflater mInflater;
-    private final PackageManager mPackageManager;
-    private final int mIconSize;
-
-    private List<ThirdPartyWallpaperTile> mThirdPartyWallpaperPickers =
-            new ArrayList<ThirdPartyWallpaperTile>();
-
-    public static class ThirdPartyWallpaperTile extends WallpaperPickerActivity.WallpaperTileInfo {
-        @Thunk ResolveInfo mResolveInfo;
-        public ThirdPartyWallpaperTile(ResolveInfo resolveInfo) {
-            mResolveInfo = resolveInfo;
-        }
-        @Override
-        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);
-            a.startActivityForResultSafely(
-                    launchIntent, WallpaperPickerActivity.PICK_WALLPAPER_THIRD_PARTY_ACTIVITY);
-        }
-    }
-
-    public ThirdPartyWallpaperPickerListAdapter(Context context) {
-        mInflater = LayoutInflater.from(context);
-        mPackageManager = context.getPackageManager();
-        mIconSize = context.getResources().getDimensionPixelSize(R.dimen.wallpaperItemIconSize);
-        final PackageManager pm = mPackageManager;
-
-        final Intent pickWallpaperIntent = new Intent(Intent.ACTION_SET_WALLPAPER);
-        final List<ResolveInfo> apps =
-                pm.queryIntentActivities(pickWallpaperIntent, 0);
-
-        // Get list of image picker intents
-        Intent pickImageIntent = new Intent(Intent.ACTION_GET_CONTENT);
-        pickImageIntent.setType("image/*");
-        final List<ResolveInfo> imagePickerActivities =
-                pm.queryIntentActivities(pickImageIntent, 0);
-        final ComponentName[] imageActivities = new ComponentName[imagePickerActivities.size()];
-        for (int i = 0; i < imagePickerActivities.size(); i++) {
-            ActivityInfo activityInfo = imagePickerActivities.get(i).activityInfo;
-            imageActivities[i] = new ComponentName(activityInfo.packageName, activityInfo.name);
-        }
-
-        outerLoop:
-        for (ResolveInfo info : apps) {
-            final ComponentName itemComponentName =
-                    new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
-            final String itemPackageName = itemComponentName.getPackageName();
-            // Exclude anything from our own package, and the old Launcher,
-            // and live wallpaper picker
-            if (itemPackageName.equals(context.getPackageName()) ||
-                    itemPackageName.equals("com.android.launcher") ||
-                    itemPackageName.equals("com.android.wallpaper.livepicker")) {
-                continue;
-            }
-            // Exclude any package that already responds to the image picker intent
-            for (ResolveInfo imagePickerActivityInfo : imagePickerActivities) {
-                if (itemPackageName.equals(
-                        imagePickerActivityInfo.activityInfo.packageName)) {
-                    continue outerLoop;
-                }
-            }
-            mThirdPartyWallpaperPickers.add(new ThirdPartyWallpaperTile(info));
-        }
-    }
-
-    public int getCount() {
-        return mThirdPartyWallpaperPickers.size();
-    }
-
-    public ThirdPartyWallpaperTile getItem(int position) {
-        return mThirdPartyWallpaperPickers.get(position);
-    }
-
-    public long getItemId(int position) {
-        return position;
-    }
-
-    public View getView(int position, View convertView, ViewGroup parent) {
-        View view;
-
-        if (convertView == null) {
-            view = mInflater.inflate(R.layout.wallpaper_picker_third_party_item, parent, false);
-        } else {
-            view = convertView;
-        }
-
-        ResolveInfo info = mThirdPartyWallpaperPickers.get(position).mResolveInfo;
-        TextView label = (TextView) view.findViewById(R.id.wallpaper_item_label);
-        label.setText(info.loadLabel(mPackageManager));
-        Drawable icon = info.loadIcon(mPackageManager);
-        icon.setBounds(new Rect(0, 0, mIconSize, mIconSize));
-        label.setCompoundDrawables(null, icon, null, null);
-        return view;
-    }
-}
diff --git a/WallpaperPicker/src/com/android/launcher3/ToggleOnTapCallback.java b/WallpaperPicker/src/com/android/launcher3/ToggleOnTapCallback.java
new file mode 100644
index 0000000..2bc48ee
--- /dev/null
+++ b/WallpaperPicker/src/com/android/launcher3/ToggleOnTapCallback.java
@@ -0,0 +1,67 @@
+package com.android.launcher3;
+
+import android.view.View;
+import android.view.ViewPropertyAnimator;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+
+import com.android.launcher3.util.Thunk;
+
+/**
+ * Callback that toggles the visibility of the target view when crop view is tapped.
+ */
+public class ToggleOnTapCallback implements CropView.TouchCallback {
+
+    @Thunk final View mViewtoToggle;
+
+    private ViewPropertyAnimator mAnim;
+    private boolean mIgnoreNextTap;
+
+    public ToggleOnTapCallback(View viewtoHide) {
+        mViewtoToggle = viewtoHide;
+    }
+
+    @Override
+    public void onTouchDown() {
+        if (mAnim != null) {
+            mAnim.cancel();
+        }
+        if (mViewtoToggle.getAlpha() == 1f) {
+            mIgnoreNextTap = true;
+        }
+
+        mAnim = mViewtoToggle.animate();
+        mAnim.alpha(0f)
+            .setDuration(150)
+            .withEndAction(new Runnable() {
+                public void run() {
+                    mViewtoToggle.setVisibility(View.INVISIBLE);
+                }
+            });
+
+        mAnim.setInterpolator(new AccelerateInterpolator(0.75f));
+        mAnim.start();
+    }
+
+    @Override
+    public void onTouchUp() {
+        mIgnoreNextTap = false;
+    }
+
+    @Override
+    public void onTap() {
+        boolean ignoreTap = mIgnoreNextTap;
+        mIgnoreNextTap = false;
+        if (!ignoreTap) {
+            if (mAnim != null) {
+                mAnim.cancel();
+            }
+            mViewtoToggle.setVisibility(View.VISIBLE);
+            mAnim = mViewtoToggle.animate();
+            mAnim.alpha(1f)
+                 .setDuration(150)
+                 .setInterpolator(new DecelerateInterpolator(0.75f));
+            mAnim.start();
+        }
+    }
+}
diff --git a/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java b/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
index eb47380..b562fbf 100644
--- a/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
+++ b/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
@@ -19,13 +19,15 @@
 import android.annotation.TargetApi;
 import android.app.ActionBar;
 import android.app.Activity;
+import android.app.WallpaperManager;
 import android.content.Intent;
-import android.content.res.Configuration;
+import android.content.pm.ActivityInfo;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
@@ -55,20 +57,10 @@
 public class WallpaperCropActivity extends BaseActivity implements Handler.Callback {
     private static final String LOGTAG = "Launcher3.CropActivity";
 
-    /**
-     * The maximum bitmap size we allow to be returned through the intent.
-     * Intents have a maximum of 1MB in total size. However, the Bitmap seems to
-     * have some overhead to hit so that we go way below the limit here to make
-     * sure the intent stays below 1MB.We should consider just returning a byte
-     * array instead of a Bitmap instance to avoid overhead.
-     */
-    public static final int MAX_BMAP_IN_INTENT = 750000;
-
     private static final int MSG_LOAD_IMAGE = 1;
 
     protected CropView mCropView;
     protected View mProgressView;
-    protected Uri mUri;
     protected View mSetWallpaperButton;
 
     private HandlerThread mLoaderThread;
@@ -89,7 +81,7 @@
 
         init();
         if (!enableRotation()) {
-            setRequestedOrientation(Configuration.ORIENTATION_PORTRAIT);
+            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
         }
     }
 
@@ -116,8 +108,9 @@
                 new View.OnClickListener() {
                     @Override
                     public void onClick(View v) {
-                        boolean finishActivityWhenDone = true;
-                        cropImageAndSetWallpaper(imageUri, null, finishActivityWhenDone);
+                        // Never fade on finish because we return to the app that started us (e.g.
+                        // Photos), not the home screen.
+                        cropImageAndSetWallpaper(imageUri, null, false /* shouldFadeOutOnFinish */);
                     }
                 });
         mSetWallpaperButton = findViewById(R.id.set_wallpaper_button);
@@ -158,52 +151,71 @@
     public boolean handleMessage(Message msg) {
         if (msg.what == MSG_LOAD_IMAGE) {
             final LoadRequest req = (LoadRequest) msg.obj;
-            try {
-                req.src.loadInBackground(new InBitmapProvider() {
+            final boolean loadSuccess;
 
-                    @Override
-                    public Bitmap forPixelCount(int count) {
-                        Bitmap bitmapToReuse = null;
-                        // Find the smallest bitmap that satisfies the pixel count limit
-                        synchronized (mReusableBitmaps) {
-                            int currentBitmapSize = Integer.MAX_VALUE;
-                            for (Bitmap b : mReusableBitmaps) {
-                                int bitmapSize = b.getWidth() * b.getHeight();
-                                if ((bitmapSize >= count) && (bitmapSize < currentBitmapSize)) {
-                                    bitmapToReuse = b;
-                                    currentBitmapSize = bitmapSize;
+            if (req.src == null) {
+                Drawable defaultWallpaper = WallpaperManager.getInstance(this)
+                        .getBuiltInDrawable(mCropView.getWidth(), mCropView.getHeight(),
+                                false, 0.5f, 0.5f);
+
+                if (defaultWallpaper == null) {
+                    loadSuccess = false;
+                    Log.w(LOGTAG, "Null default wallpaper encountered.");
+                } else {
+                    loadSuccess = true;
+                    req.result = new DrawableTileSource(this,
+                            defaultWallpaper, DrawableTileSource.MAX_PREVIEW_SIZE);
+                }
+            } else {
+                try {
+                    req.src.loadInBackground(new InBitmapProvider() {
+
+                        @Override
+                        public Bitmap forPixelCount(int count) {
+                            Bitmap bitmapToReuse = null;
+                            // Find the smallest bitmap that satisfies the pixel count limit
+                            synchronized (mReusableBitmaps) {
+                                int currentBitmapSize = Integer.MAX_VALUE;
+                                for (Bitmap b : mReusableBitmaps) {
+                                    int bitmapSize = b.getWidth() * b.getHeight();
+                                    if ((bitmapSize >= count) && (bitmapSize < currentBitmapSize)) {
+                                        bitmapToReuse = b;
+                                        currentBitmapSize = bitmapSize;
+                                    }
+                                }
+
+                                if (bitmapToReuse != null) {
+                                    mReusableBitmaps.remove(bitmapToReuse);
                                 }
                             }
-
-                            if (bitmapToReuse != null) {
-                                mReusableBitmaps.remove(bitmapToReuse);
-                            }
+                            return bitmapToReuse;
                         }
-                        return bitmapToReuse;
+                    });
+                } catch (SecurityException securityException) {
+                    if (isActivityDestroyed()) {
+                        // Temporarily granted permissions are revoked when the activity
+                        // finishes, potentially resulting in a SecurityException here.
+                        // Even though {@link #isDestroyed} might also return true in different
+                        // situations where the configuration changes, we are fine with
+                        // catching these cases here as well.
+                        return true;
+                    } else {
+                        // otherwise it had a different cause and we throw it further
+                        throw securityException;
                     }
-                });
-            } catch (SecurityException securityException) {
-                if (isActivityDestroyed()) {
-                    // Temporarily granted permissions are revoked when the activity
-                    // finishes, potentially resulting in a SecurityException here.
-                    // Even though {@link #isDestroyed} might also return true in different
-                    // situations where the configuration changes, we are fine with
-                    // catching these cases here as well.
-                    return true;
-                } else {
-                    // otherwise it had a different cause and we throw it further
-                    throw securityException;
                 }
+
+                req.result = new BitmapRegionTileSource(getContext(), req.src,
+                        mTempStorageForDecoding);
+                loadSuccess = req.src.getLoadingState() == BitmapSource.State.LOADED;
             }
 
-            req.result = new BitmapRegionTileSource(getContext(), req.src, mTempStorageForDecoding);
             runOnUiThread(new Runnable() {
 
                 @Override
                 public void run() {
                     if (req == mCurrentLoadRequest) {
-                        onLoadRequestComplete(req,
-                                req.src.getLoadingState() == BitmapSource.State.LOADED);
+                        onLoadRequestComplete(req, loadSuccess);
                     } else {
                         addReusableBitmap(req.result);
                     }
@@ -215,7 +227,7 @@
     }
 
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
-    protected boolean isActivityDestroyed() {
+    public boolean isActivityDestroyed() {
         return Utilities.ATLEAST_JB_MR1 && isDestroyed();
     }
 
@@ -239,8 +251,14 @@
             if (req.moveToLeft) {
                 mCropView.moveToLeft();
             }
-            if (req.scaleProvider != null) {
-                mCropView.setScale(req.scaleProvider.getScale(req.result));
+            if (req.scaleAndOffsetProvider != null) {
+                TileSource src = req.result;
+                Point wallpaperSize = WallpaperUtils.getDefaultWallpaperSize(
+                        getResources(), getWindowManager());
+                RectF crop = Utils.getMaxCropRect(src.getImageWidth(), src.getImageHeight(),
+                        wallpaperSize.x, wallpaperSize.y, false /* leftAligned */);
+                mCropView.setScale(req.scaleAndOffsetProvider.getScale(wallpaperSize, crop));
+                mCropView.setParallaxOffset(req.scaleAndOffsetProvider.getParallaxOffset(), crop);
             }
 
             // Free last image
@@ -257,14 +275,16 @@
         mProgressView.setVisibility(View.GONE);
     }
 
+    @TargetApi(Build.VERSION_CODES.KITKAT)
     public final void setCropViewTileSource(BitmapSource bitmapSource, boolean touchEnabled,
-            boolean moveToLeft, CropViewScaleProvider scaleProvider, Runnable postExecute) {
+            boolean moveToLeft, CropViewScaleAndOffsetProvider scaleAndOffsetProvider,
+            Runnable postExecute) {
         final LoadRequest req = new LoadRequest();
         req.moveToLeft = moveToLeft;
         req.src = bitmapSource;
         req.touchEnabled = touchEnabled;
         req.postExecute = postExecute;
-        req.scaleProvider = scaleProvider;
+        req.scaleAndOffsetProvider = scaleAndOffsetProvider;
         mCurrentLoadRequest = req;
 
         // Remove any pending requests
@@ -288,27 +308,18 @@
         return getResources().getBoolean(R.bool.allow_rotation);
     }
 
-    protected void setWallpaper(Uri uri, final boolean finishActivityWhenDone) {
+    public void setWallpaper(Uri uri, boolean shouldFadeOutOnFinish) {
         int rotation = BitmapUtils.getRotationFromExif(getContext(), uri);
         BitmapCropTask cropTask = new BitmapCropTask(
                 getContext(), uri, null, rotation, 0, 0, true, false, null);
-        final Point bounds = cropTask.getImageBounds();
-        Runnable onEndCrop = new Runnable() {
-            public void run() {
-                WallpaperUtils.saveWallpaperDimensions(bounds.x, bounds.y, WallpaperCropActivity.this);
-                if (finishActivityWhenDone) {
-                    setResult(Activity.RESULT_OK);
-                    finish();
-                }
-            }
-        };
-        cropTask.setOnEndRunnable(onEndCrop);
+        BitmapCropTask.OnEndCropHandler onEndCrop = new CropAndFinishHandler(
+                cropTask.getImageBounds(), shouldFadeOutOnFinish);
+        cropTask.setOnEndCropHandler(onEndCrop);
         cropTask.setNoCrop(true);
         cropTask.execute();
     }
 
-    protected void cropImageAndSetWallpaper(
-            Resources res, int resId, final boolean finishActivityWhenDone) {
+    public void cropImageAndSetWallpaper(Resources res, int resId, boolean shouldFadeOutOnFinish) {
         // crop this image and scale it down to the default wallpaper size for
         // this device
         int rotation = BitmapUtils.getRotationFromExif(res, resId, this);
@@ -317,25 +328,19 @@
                 getWindowManager());
         RectF crop = Utils.getMaxCropRect(
                 inSize.x, inSize.y, outSize.x, outSize.y, false);
-        Runnable onEndCrop = new Runnable() {
-            public void run() {
-                // Passing 0, 0 will cause launcher to revert to using the
-                // default wallpaper size
-                WallpaperUtils.saveWallpaperDimensions(0, 0, WallpaperCropActivity.this);
-                if (finishActivityWhenDone) {
-                    setResult(Activity.RESULT_OK);
-                    finish();
-                }
-            }
-        };
+        // Passing 0, 0 will cause launcher to revert to using the
+        // default wallpaper size
+        CropAndFinishHandler onEndCrop = new CropAndFinishHandler(new Point(0, 0),
+                shouldFadeOutOnFinish);
         BitmapCropTask cropTask = new BitmapCropTask(getContext(), res, resId,
                 crop, rotation, outSize.x, outSize.y, true, false, onEndCrop);
         cropTask.execute();
     }
 
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
-    protected void cropImageAndSetWallpaper(Uri uri,
-            BitmapCropTask.OnBitmapCroppedHandler onBitmapCroppedHandler, final boolean finishActivityWhenDone) {
+    public void cropImageAndSetWallpaper(Uri uri,
+            BitmapCropTask.OnBitmapCroppedHandler onBitmapCroppedHandler,
+            boolean shouldFadeOutOnFinish) {
         boolean centerCrop = getResources().getBoolean(R.bool.center_crop);
         // Get the crop
         boolean ltr = mCropView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
@@ -408,18 +413,12 @@
             cropRect.top -= expandHeight;
             cropRect.bottom += expandHeight;
         }
+
         final int outWidth = (int) Math.round(cropRect.width() * cropScale);
         final int outHeight = (int) Math.round(cropRect.height() * cropScale);
+        CropAndFinishHandler onEndCrop = new CropAndFinishHandler(new Point(outWidth, outHeight),
+                shouldFadeOutOnFinish);
 
-        Runnable onEndCrop = new Runnable() {
-            public void run() {
-                WallpaperUtils.saveWallpaperDimensions(outWidth, outHeight, WallpaperCropActivity.this);
-                if (finishActivityWhenDone) {
-                    setResult(Activity.RESULT_OK);
-                    finish();
-                }
-            }
-        };
         BitmapCropTask cropTask = new BitmapCropTask(getContext(), uri,
                 cropRect, cropRotation, outWidth, outHeight, true, false, onEndCrop);
         if (onBitmapCroppedHandler != null) {
@@ -428,17 +427,44 @@
         cropTask.execute();
     }
 
+    public class CropAndFinishHandler implements BitmapCropTask.OnEndCropHandler {
+        private final Point mBounds;
+        private boolean mShouldFadeOutOnFinish;
+
+        /**
+         * @param shouldFadeOutOnFinish Whether the wallpaper picker should override the default
+         * exit animation to fade out instead. This should only be set to true if the wallpaper
+         * preview will exactly match the actual wallpaper on the page we are returning to.
+         */
+        public CropAndFinishHandler(Point bounds, boolean shouldFadeOutOnFinish) {
+            mBounds = bounds;
+            mShouldFadeOutOnFinish = shouldFadeOutOnFinish;
+        }
+
+        @Override
+        public void run(boolean cropSucceeded) {
+            WallpaperUtils.saveWallpaperDimensions(mBounds.x, mBounds.y,
+                    WallpaperCropActivity.this);
+            setResult(Activity.RESULT_OK);
+            finish();
+            if (cropSucceeded && mShouldFadeOutOnFinish) {
+                overridePendingTransition(0, R.anim.fade_out);
+            }
+        }
+    }
+
     static class LoadRequest {
         BitmapSource src;
         boolean touchEnabled;
         boolean moveToLeft;
         Runnable postExecute;
-        CropViewScaleProvider scaleProvider;
+        CropViewScaleAndOffsetProvider scaleAndOffsetProvider;
 
         TileSource result;
     }
 
-    interface CropViewScaleProvider {
-        float getScale(TileSource src);
+    public interface CropViewScaleAndOffsetProvider {
+        float getScale(Point wallpaperSize, RectF crop);
+        float getParallaxOffset();
     }
 }
diff --git a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
index e2c008b..27d60f8 100644
--- a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
+++ b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
@@ -16,38 +16,22 @@
 
 package com.android.launcher3;
 
-import android.Manifest;
 import android.animation.LayoutTransition;
-import android.annotation.TargetApi;
+import android.annotation.SuppressLint;
 import android.app.ActionBar;
 import android.app.Activity;
-import android.app.WallpaperManager;
-import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
-import android.database.Cursor;
-import android.database.DataSetObserver;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Point;
-import android.graphics.PorterDuff;
-import android.graphics.RectF;
 import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
 import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Build;
 import android.os.Bundle;
-import android.os.Process;
-import android.provider.MediaStore;
 import android.util.Log;
 import android.util.Pair;
 import android.view.ActionMode;
-import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
@@ -55,280 +39,56 @@
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.View.OnLayoutChangeListener;
+import android.view.View.OnLongClickListener;
 import android.view.ViewGroup;
-import android.view.ViewPropertyAnimator;
 import android.view.ViewTreeObserver;
 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
 import android.view.WindowManager;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.DecelerateInterpolator;
-import android.widget.ArrayAdapter;
-import android.widget.BaseAdapter;
-import android.widget.FrameLayout;
 import android.widget.HorizontalScrollView;
-import android.widget.ImageView;
 import android.widget.LinearLayout;
-import android.widget.Toast;
 
-import com.android.gallery3d.common.BitmapCropTask;
-import com.android.gallery3d.common.BitmapUtils;
-import com.android.gallery3d.common.Utils;
 import com.android.launcher3.util.Thunk;
-import com.android.launcher3.util.WallpaperUtils;
-import com.android.photos.BitmapRegionTileSource;
-import com.android.photos.BitmapRegionTileSource.BitmapSource;
-import com.android.photos.views.TiledImageRenderer.TileSource;
+import com.android.launcher3.wallpapertileinfo.DefaultWallpaperInfo;
+import com.android.launcher3.wallpapertileinfo.FileWallpaperInfo;
+import com.android.launcher3.wallpapertileinfo.LiveWallpaperInfo;
+import com.android.launcher3.wallpapertileinfo.PickImageInfo;
+import com.android.launcher3.wallpapertileinfo.ResourceWallpaperInfo;
+import com.android.launcher3.wallpapertileinfo.ThirdPartyWallpaperInfo;
+import com.android.launcher3.wallpapertileinfo.UriWallpaperInfo;
+import com.android.launcher3.wallpapertileinfo.WallpaperTileInfo;
 
 import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
 import java.util.ArrayList;
+import java.util.List;
 
-public class WallpaperPickerActivity extends WallpaperCropActivity {
+public class WallpaperPickerActivity extends WallpaperCropActivity
+        implements OnClickListener, OnLongClickListener, ActionMode.Callback {
     static final String TAG = "Launcher.WallpaperPickerActivity";
 
     public static final int IMAGE_PICK = 5;
     public static final int PICK_WALLPAPER_THIRD_PARTY_ACTIVITY = 6;
+    /** An Intent extra used when opening the wallpaper picker from the workspace overlay. */
+    public static final String EXTRA_WALLPAPER_OFFSET = "com.android.launcher3.WALLPAPER_OFFSET";
     private static final String TEMP_WALLPAPER_TILES = "TEMP_WALLPAPER_TILES";
     private static final String SELECTED_INDEX = "SELECTED_INDEX";
     private static final int FLAG_POST_DELAY_MILLIS = 200;
 
-    @Thunk View mSelectedTile;
-    @Thunk boolean mIgnoreNextTap;
-    @Thunk OnClickListener mThumbnailOnClickListener;
+    @Thunk
+    View mSelectedTile;
 
     @Thunk LinearLayout mWallpapersView;
     @Thunk HorizontalScrollView mWallpaperScrollContainer;
     @Thunk View mWallpaperStrip;
 
-    @Thunk ActionMode.Callback mActionModeCallback;
     @Thunk ActionMode mActionMode;
 
-    @Thunk View.OnLongClickListener mLongClickListener;
-
     ArrayList<Uri> mTempWallpaperTiles = new ArrayList<Uri>();
     private SavedWallpaperImages mSavedImages;
     @Thunk int mSelectedIndex = -1;
-
-    public static abstract class WallpaperTileInfo {
-        protected View mView;
-        public Drawable mThumb;
-
-        public void setView(View v) {
-            mView = v;
-        }
-        public void onClick(WallpaperPickerActivity a) {}
-        public void onSave(WallpaperPickerActivity a) {}
-        public void onDelete(WallpaperPickerActivity a) {}
-        public boolean isSelectable() { return false; }
-        public boolean isNamelessWallpaper() { return false; }
-        public void onIndexUpdated(CharSequence label) {
-            if (isNamelessWallpaper()) {
-                mView.setContentDescription(label);
-            }
-        }
-    }
-
-    public static class PickImageInfo extends WallpaperTileInfo {
-        @Override
-        public void onClick(WallpaperPickerActivity a) {
-            Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
-            intent.setType("image/*");
-            a.startActivityForResultSafely(intent, IMAGE_PICK);
-        }
-    }
-
-    public static class UriWallpaperInfo extends WallpaperTileInfo {
-        private Uri mUri;
-        public UriWallpaperInfo(Uri uri) {
-            mUri = uri;
-        }
-        @Override
-        public void onClick(final WallpaperPickerActivity a) {
-            a.setWallpaperButtonEnabled(false);
-            final BitmapRegionTileSource.UriBitmapSource bitmapSource =
-                    new BitmapRegionTileSource.UriBitmapSource(a.getContext(), mUri);
-            a.setCropViewTileSource(bitmapSource, true, false, null, new Runnable() {
-
-                @Override
-                public void run() {
-                    if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) {
-                        a.selectTile(mView);
-                        a.setWallpaperButtonEnabled(true);
-                    } else {
-                        ViewGroup parent = (ViewGroup) mView.getParent();
-                        if (parent != null) {
-                            parent.removeView(mView);
-                            Toast.makeText(a.getContext(), R.string.image_load_fail,
-                                    Toast.LENGTH_SHORT).show();
-                        }
-                    }
-                }
-            });
-        }
-        @Override
-        public void onSave(final WallpaperPickerActivity a) {
-            boolean finishActivityWhenDone = true;
-            BitmapCropTask.OnBitmapCroppedHandler h = new BitmapCropTask.OnBitmapCroppedHandler() {
-                public void onBitmapCropped(byte[] imageBytes) {
-                    Point thumbSize = getDefaultThumbnailSize(a.getResources());
-                    // rotation is set to 0 since imageBytes has already been correctly rotated
-                    Bitmap thumb = createThumbnail(
-                            thumbSize, null, null, imageBytes, null, 0, 0, true);
-                    a.getSavedImages().writeImage(thumb, imageBytes);
-                }
-            };
-            a.cropImageAndSetWallpaper(mUri, h, finishActivityWhenDone);
-        }
-        @Override
-        public boolean isSelectable() {
-            return true;
-        }
-        @Override
-        public boolean isNamelessWallpaper() {
-            return true;
-        }
-    }
-
-    public static class FileWallpaperInfo extends WallpaperTileInfo {
-        private File mFile;
-
-        public FileWallpaperInfo(File target, Drawable thumb) {
-            mFile = target;
-            mThumb = thumb;
-        }
-        @Override
-        public void onClick(final WallpaperPickerActivity a) {
-            a.setWallpaperButtonEnabled(false);
-            final BitmapRegionTileSource.FilePathBitmapSource bitmapSource =
-                    new BitmapRegionTileSource.FilePathBitmapSource(mFile.getAbsolutePath());
-            a.setCropViewTileSource(bitmapSource, false, true, null, new Runnable() {
-
-                @Override
-                public void run() {
-                    if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) { 
-                        a.setWallpaperButtonEnabled(true);
-                    }
-                }
-            });
-        }
-        @Override
-        public void onSave(WallpaperPickerActivity a) {
-            a.setWallpaper(Uri.fromFile(mFile), true);
-        }
-        @Override
-        public boolean isSelectable() {
-            return true;
-        }
-        @Override
-        public boolean isNamelessWallpaper() {
-            return true;
-        }
-    }
-
-    public static class ResourceWallpaperInfo extends WallpaperTileInfo {
-        private Resources mResources;
-        private int mResId;
-
-        public ResourceWallpaperInfo(Resources res, int resId, Drawable thumb) {
-            mResources = res;
-            mResId = resId;
-            mThumb = thumb;
-        }
-        @Override
-        public void onClick(final WallpaperPickerActivity a) {
-            a.setWallpaperButtonEnabled(false);
-            final BitmapRegionTileSource.ResourceBitmapSource bitmapSource =
-                    new BitmapRegionTileSource.ResourceBitmapSource(mResources, mResId, a);
-            a.setCropViewTileSource(bitmapSource, false, false, new CropViewScaleProvider() {
-
-                @Override
-                public float getScale(TileSource src) {
-                    Point wallpaperSize = WallpaperUtils.getDefaultWallpaperSize(
-                            a.getResources(), a.getWindowManager());
-                    RectF crop = Utils.getMaxCropRect(
-                            src.getImageWidth(), src.getImageHeight(),
-                            wallpaperSize.x, wallpaperSize.y, false);
-                    return wallpaperSize.x / crop.width();
-                }
-            }, new Runnable() {
-
-                @Override
-                public void run() {
-                    if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) {
-                        a.setWallpaperButtonEnabled(true);
-                    }
-                }
-            });
-        }
-        @Override
-        public void onSave(WallpaperPickerActivity a) {
-            boolean finishActivityWhenDone = true;
-            a.cropImageAndSetWallpaper(mResources, mResId, finishActivityWhenDone);
-        }
-        @Override
-        public boolean isSelectable() {
-            return true;
-        }
-        @Override
-        public boolean isNamelessWallpaper() {
-            return true;
-        }
-    }
-
-    @TargetApi(Build.VERSION_CODES.KITKAT)
-    public static class DefaultWallpaperInfo extends WallpaperTileInfo {
-        public DefaultWallpaperInfo(Drawable thumb) {
-            mThumb = thumb;
-        }
-        @Override
-        public void onClick(WallpaperPickerActivity a) {
-            CropView c = a.getCropView();
-            Drawable defaultWallpaper = WallpaperManager.getInstance(a.getContext())
-                    .getBuiltInDrawable(c.getWidth(), c.getHeight(), false, 0.5f, 0.5f);
-            if (defaultWallpaper == null) {
-                Log.w(TAG, "Null default wallpaper encountered.");
-                c.setTileSource(null, null);
-                return;
-            }
-
-            LoadRequest req = new LoadRequest();
-            req.moveToLeft = false;
-            req.touchEnabled = false;
-            req.scaleProvider = new CropViewScaleProvider() {
-
-                @Override
-                public float getScale(TileSource src) {
-                    return 1f;
-                }
-            };
-            req.result = new DrawableTileSource(a.getContext(),
-                    defaultWallpaper, DrawableTileSource.MAX_PREVIEW_SIZE);
-            a.onLoadRequestComplete(req, true);
-        }
-        @Override
-        public void onSave(WallpaperPickerActivity a) {
-            try {
-                WallpaperManager.getInstance(a.getContext()).clear();
-                a.setResult(Activity.RESULT_OK);
-            } catch (IOException e) {
-                Log.w("Setting wallpaper to default threw exception", e);
-            }
-            a.finish();
-        }
-        @Override
-        public boolean isSelectable() {
-            return true;
-        }
-        @Override
-        public boolean isNamelessWallpaper() {
-            return true;
-        }
-    }
+    private float mWallpaperParallaxOffset;
 
     /**
-     * shows the system wallpaper behind the window and hides the {@link
-     * #mCropView} if visible
+     * shows the system wallpaper behind the window and hides the {@link #mCropView} if visible
      * @param visible should the system wallpaper be shown
      */
     protected void setSystemWallpaperVisiblity(final boolean visible) {
@@ -370,7 +130,9 @@
         }
     }
 
-    // called by onCreate; this is subclassed to overwrite WallpaperCropActivity
+    /**
+     * called by onCreate; this is sub-classed to overwrite WallpaperCropActivity
+     */
     protected void init() {
         setContentView(R.layout.wallpaper_picker);
 
@@ -380,137 +142,39 @@
         mProgressView = findViewById(R.id.loading);
         mWallpaperScrollContainer = (HorizontalScrollView) findViewById(R.id.wallpaper_scroll_container);
         mWallpaperStrip = findViewById(R.id.wallpaper_strip);
-        mCropView.setTouchCallback(new CropView.TouchCallback() {
-            ViewPropertyAnimator mAnim;
-            @Override
-            public void onTouchDown() {
-                if (mAnim != null) {
-                    mAnim.cancel();
-                }
-                if (mWallpaperStrip.getAlpha() == 1f) {
-                    mIgnoreNextTap = true;
-                }
-                mAnim = mWallpaperStrip.animate();
-                mAnim.alpha(0f)
-                    .setDuration(150)
-                    .withEndAction(new Runnable() {
-                        public void run() {
-                            mWallpaperStrip.setVisibility(View.INVISIBLE);
-                        }
-                    });
-                mAnim.setInterpolator(new AccelerateInterpolator(0.75f));
-                mAnim.start();
-            }
-            @Override
-            public void onTouchUp() {
-                mIgnoreNextTap = false;
-            }
-            @Override
-            public void onTap() {
-                boolean ignoreTap = mIgnoreNextTap;
-                mIgnoreNextTap = false;
-                if (!ignoreTap) {
-                    if (mAnim != null) {
-                        mAnim.cancel();
-                    }
-                    mWallpaperStrip.setVisibility(View.VISIBLE);
-                    mAnim = mWallpaperStrip.animate();
-                    mAnim.alpha(1f)
-                         .setDuration(150)
-                         .setInterpolator(new DecelerateInterpolator(0.75f));
-                    mAnim.start();
-                }
-            }
-        });
+        mCropView.setTouchCallback(new ToggleOnTapCallback(mWallpaperStrip));
 
-        mThumbnailOnClickListener = new OnClickListener() {
-            public void onClick(View v) {
-                if (mActionMode != null) {
-                    // When CAB is up, clicking toggles the item instead
-                    if (v.isLongClickable()) {
-                        mLongClickListener.onLongClick(v);
-                    }
-                    return;
-                }
-                setWallpaperButtonEnabled(true);
-                WallpaperTileInfo info = (WallpaperTileInfo) v.getTag();
-                if (info.isSelectable() && v.getVisibility() == View.VISIBLE) {
-                    selectTile(v);
-                }
-                info.onClick(WallpaperPickerActivity.this);
-            }
-        };
-        mLongClickListener = new View.OnLongClickListener() {
-            // Called when the user long-clicks on someView
-            public boolean onLongClick(View view) {
-                CheckableFrameLayout c = (CheckableFrameLayout) view;
-                c.toggle();
+        mWallpaperParallaxOffset = getIntent().getFloatExtra(EXTRA_WALLPAPER_OFFSET, 0);
 
-                if (mActionMode != null) {
-                    mActionMode.invalidate();
-                } else {
-                    // Start the CAB using the ActionMode.Callback defined below
-                    mActionMode = startActionMode(mActionModeCallback);
-                    int childCount = mWallpapersView.getChildCount();
-                    for (int i = 0; i < childCount; i++) {
-                        mWallpapersView.getChildAt(i).setSelected(false);
-                    }
-                }
-                return true;
-            }
-        };
+        mWallpapersView = (LinearLayout) findViewById(R.id.wallpaper_list);
+        // Populate the saved wallpapers
+        mSavedImages = new SavedWallpaperImages(getContext());
+        populateWallpapers(mWallpapersView, mSavedImages.loadThumbnailsAndImageIdList(), true);
 
         // Populate the built-in wallpapers
         ArrayList<WallpaperTileInfo> wallpapers = findBundledWallpapers();
-        mWallpapersView = (LinearLayout) findViewById(R.id.wallpaper_list);
-        SimpleWallpapersAdapter ia = new SimpleWallpapersAdapter(getContext(), wallpapers);
-        populateWallpapersFromAdapter(mWallpapersView, ia, false);
+        populateWallpapers(mWallpapersView, wallpapers, false);
 
-        // Populate the saved wallpapers
-        mSavedImages = new SavedWallpaperImages(getContext());
-        mSavedImages.loadThumbnailsAndImageIdList();
-        populateWallpapersFromAdapter(mWallpapersView, mSavedImages, true);
+        // Load live wallpapers asynchronously
+        new LiveWallpaperInfo.LoaderTask(this) {
 
-        // Populate the live wallpapers
-        final LinearLayout liveWallpapersView =
-                (LinearLayout) findViewById(R.id.live_wallpaper_list);
-        final LiveWallpaperListAdapter a = new LiveWallpaperListAdapter(getContext());
-        a.registerDataSetObserver(new DataSetObserver() {
-            public void onChanged() {
-                liveWallpapersView.removeAllViews();
-                populateWallpapersFromAdapter(liveWallpapersView, a, false);
+            @Override
+            protected void onPostExecute(List<LiveWallpaperInfo> result) {
+                populateWallpapers((LinearLayout) findViewById(R.id.live_wallpaper_list),
+                        result, false);
                 initializeScrollForRtl();
                 updateTileIndices();
             }
-        });
+        }.execute();
 
         // Populate the third-party wallpaper pickers
-        final LinearLayout thirdPartyWallpapersView =
-                (LinearLayout) findViewById(R.id.third_party_wallpaper_list);
-        final ThirdPartyWallpaperPickerListAdapter ta =
-                new ThirdPartyWallpaperPickerListAdapter(getContext());
-        populateWallpapersFromAdapter(thirdPartyWallpapersView, ta, false);
+        populateWallpapers((LinearLayout) findViewById(R.id.third_party_wallpaper_list),
+                ThirdPartyWallpaperInfo.getAll(this), false /* addLongPressHandler */);
 
         // Add a tile for the Gallery
         LinearLayout masterWallpaperList = (LinearLayout) findViewById(R.id.master_wallpaper_list);
-        FrameLayout pickImageTile = (FrameLayout) getLayoutInflater().
-                inflate(R.layout.wallpaper_picker_image_picker_item, masterWallpaperList, false);
-        masterWallpaperList.addView(pickImageTile, 0);
-
-        // Make its background the last photo taken on external storage
-        Bitmap lastPhoto = getThumbnailOfLastPhoto();
-        if (lastPhoto != null) {
-            ImageView galleryThumbnailBg =
-                    (ImageView) pickImageTile.findViewById(R.id.wallpaper_image);
-            galleryThumbnailBg.setImageBitmap(lastPhoto);
-            int colorOverlay = getResources().getColor(R.color.wallpaper_picker_translucent_gray);
-            galleryThumbnailBg.setColorFilter(colorOverlay, PorterDuff.Mode.SRC_ATOP);
-        }
-
-        PickImageInfo pickImageInfo = new PickImageInfo();
-        pickImageTile.setTag(pickImageInfo);
-        pickImageInfo.setView(pickImageTile);
-        pickImageTile.setOnClickListener(mThumbnailOnClickListener);
+        masterWallpaperList.addView(
+                createTileView(masterWallpaperList, new PickImageInfo(), false), 0);
 
         // Select the first item; wait for a layout pass so that we initialize the dimensions of
         // cropView or the defaultWallpaperView first
@@ -520,8 +184,7 @@
                     int oldLeft, int oldTop, int oldRight, int oldBottom) {
                 if ((right - left) > 0 && (bottom - top) > 0) {
                     if (mSelectedIndex >= 0 && mSelectedIndex < mWallpapersView.getChildCount()) {
-                        mThumbnailOnClickListener.onClick(
-                                mWallpapersView.getChildAt(mSelectedIndex));
+                        onClick(mWallpapersView.getChildAt(mSelectedIndex));
                         setSystemWallpaperVisiblity(false);
                     }
                     v.removeOnLayoutChangeListener(this);
@@ -565,102 +228,58 @@
                     }
                 });
         mSetWallpaperButton = findViewById(R.id.set_wallpaper_button);
+    }
 
-        // CAB for deleting items
-        mActionModeCallback = new ActionMode.Callback() {
-            // Called when the action mode is created; startActionMode() was called
-            @Override
-            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
-                // Inflate a menu resource providing context menu items
-                MenuInflater inflater = mode.getMenuInflater();
-                inflater.inflate(R.menu.cab_delete_wallpapers, menu);
-                return true;
+    /**
+     * Called when a wallpaper tile is clicked
+     */
+    @Override
+    public void onClick(View v) {
+        if (mActionMode != null) {
+            // When CAB is up, clicking toggles the item instead
+            if (v.isLongClickable()) {
+                onLongClick(v);
             }
+            return;
+        }
+        setWallpaperButtonEnabled(true);
+        WallpaperTileInfo info = (WallpaperTileInfo) v.getTag();
+        if (info.isSelectable() && v.getVisibility() == View.VISIBLE) {
+            selectTile(v);
+        }
+        info.onClick(this);
+    }
 
-            private int numCheckedItems() {
-                int childCount = mWallpapersView.getChildCount();
-                int numCheckedItems = 0;
-                for (int i = 0; i < childCount; i++) {
-                    CheckableFrameLayout c = (CheckableFrameLayout) mWallpapersView.getChildAt(i);
-                    if (c.isChecked()) {
-                        numCheckedItems++;
-                    }
-                }
-                return numCheckedItems;
-            }
+    /**
+     * Called when a view is long clicked
+     */
+    @Override
+    public boolean onLongClick(View v) {
+        CheckableFrameLayout c = (CheckableFrameLayout) v;
+        c.toggle();
 
-            // Called each time the action mode is shown. Always called after onCreateActionMode,
-            // but may be called multiple times if the mode is invalidated.
-            @Override
-            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
-                int numCheckedItems = numCheckedItems();
-                if (numCheckedItems == 0) {
-                    mode.finish();
-                    return true;
-                } else {
-                    mode.setTitle(getResources().getQuantityString(
-                            R.plurals.number_of_items_selected, numCheckedItems, numCheckedItems));
-                    return true;
-                }
+        if (mActionMode != null) {
+            mActionMode.invalidate();
+        } else {
+            // Start the CAB using the ActionMode.Callback defined below
+            mActionMode = startActionMode(this);
+            int childCount = mWallpapersView.getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                mWallpapersView.getChildAt(i).setSelected(false);
             }
-
-            // Called when the user selects a contextual menu item
-            @Override
-            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
-                int itemId = item.getItemId();
-                if (itemId == R.id.menu_delete) {
-                    int childCount = mWallpapersView.getChildCount();
-                    ArrayList<View> viewsToRemove = new ArrayList<View>();
-                    boolean selectedTileRemoved = false;
-                    for (int i = 0; i < childCount; i++) {
-                        CheckableFrameLayout c =
-                                (CheckableFrameLayout) mWallpapersView.getChildAt(i);
-                        if (c.isChecked()) {
-                            WallpaperTileInfo info = (WallpaperTileInfo) c.getTag();
-                            info.onDelete(WallpaperPickerActivity.this);
-                            viewsToRemove.add(c);
-                            if (i == mSelectedIndex) {
-                                selectedTileRemoved = true;
-                            }
-                        }
-                    }
-                    for (View v : viewsToRemove) {
-                        mWallpapersView.removeView(v);
-                    }
-                    if (selectedTileRemoved) {
-                        mSelectedIndex = -1;
-                        mSelectedTile = null;
-                        setSystemWallpaperVisiblity(true);
-                    }
-                    updateTileIndices();
-                    mode.finish(); // Action picked, so close the CAB
-                    return true;
-                } else {
-                    return false;
-                }
-            }
-
-            // Called when the user exits the action mode
-            @Override
-            public void onDestroyActionMode(ActionMode mode) {
-                int childCount = mWallpapersView.getChildCount();
-                for (int i = 0; i < childCount; i++) {
-                    CheckableFrameLayout c = (CheckableFrameLayout) mWallpapersView.getChildAt(i);
-                    c.setChecked(false);
-                }
-                if (mSelectedTile != null) {
-                    mSelectedTile.setSelected(true);
-                }
-                mActionMode = null;
-            }
-        };
+        }
+        return true;
     }
 
     public void setWallpaperButtonEnabled(boolean enabled) {
         mSetWallpaperButton.setEnabled(enabled);
     }
 
-    @Thunk void selectTile(View v) {
+    public float getWallpaperParallaxOffset() {
+        return mWallpaperParallaxOffset;
+    }
+
+    public void selectTile(View v) {
         if (mSelectedTile != null) {
             mSelectedTile.setSelected(false);
             mSelectedTile = null;
@@ -688,35 +307,6 @@
         }
     }
 
-    protected Bitmap getThumbnailOfLastPhoto() {
-        boolean canReadExternalStorage = getActivity().checkPermission(
-                Manifest.permission.READ_EXTERNAL_STORAGE, Process.myPid(), Process.myUid()) ==
-                PackageManager.PERMISSION_GRANTED;
-
-        if (!canReadExternalStorage) {
-            // MediaStore.Images.Media.EXTERNAL_CONTENT_URI requires
-            // the READ_EXTERNAL_STORAGE permission
-            return null;
-        }
-
-        Cursor cursor = MediaStore.Images.Media.query(getContext().getContentResolver(),
-                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
-                new String[] { MediaStore.Images.ImageColumns._ID,
-                    MediaStore.Images.ImageColumns.DATE_TAKEN},
-                null, null, MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC LIMIT 1");
-
-        Bitmap thumb = null;
-        if (cursor != null) {
-            if (cursor.moveToNext()) {
-                int id = cursor.getInt(0);
-                thumb = MediaStore.Images.Thumbnails.getThumbnail(getContext().getContentResolver(),
-                        id, MediaStore.Images.Thumbnails.MINI_KIND, null);
-            }
-            cursor.close();
-        }
-        return thumb;
-    }
-
     public void onStop() {
         super.onStop();
         mWallpaperStrip = findViewById(R.id.wallpaper_strip);
@@ -739,21 +329,6 @@
         mSelectedIndex = savedInstanceState.getInt(SELECTED_INDEX, -1);
     }
 
-    @Thunk void populateWallpapersFromAdapter(ViewGroup parent, BaseAdapter adapter,
-            boolean addLongPressHandler) {
-        for (int i = 0; i < adapter.getCount(); i++) {
-            FrameLayout thumbnail = (FrameLayout) adapter.getView(i, null, parent);
-            parent.addView(thumbnail, i);
-            WallpaperTileInfo info = (WallpaperTileInfo) adapter.getItem(i);
-            thumbnail.setTag(info);
-            info.setView(thumbnail);
-            if (addLongPressHandler) {
-                addLongPressHandler(thumbnail);
-            }
-            thumbnail.setOnClickListener(mThumbnailOnClickListener);
-        }
-    }
-
     @Thunk void updateTileIndices() {
         LinearLayout masterWallpaperList = (LinearLayout) findViewById(R.id.master_wallpaper_list);
         final int childCount = masterWallpaperList.getChildCount();
@@ -795,104 +370,66 @@
         }
     }
 
-    @Thunk static Point getDefaultThumbnailSize(Resources res) {
-        return new Point(res.getDimensionPixelSize(R.dimen.wallpaperThumbnailWidth),
-                res.getDimensionPixelSize(R.dimen.wallpaperThumbnailHeight));
-
-    }
-
-    @Thunk static Bitmap createThumbnail(Point size, Context context, Uri uri, byte[] imageBytes,
-            Resources res, int resId, int rotation, boolean leftAligned) {
-        int width = size.x;
-        int height = size.y;
-
-        BitmapCropTask cropTask;
-        if (uri != null) {
-            cropTask = new BitmapCropTask(
-                    context, uri, null, rotation, width, height, false, true, null);
-        } else if (imageBytes != null) {
-            cropTask = new BitmapCropTask(
-                    imageBytes, null, rotation, width, height, false, true, null);
-        }  else {
-            cropTask = new BitmapCropTask(
-                    context, res, resId, null, rotation, width, height, false, true, null);
-        }
-        Point bounds = cropTask.getImageBounds();
-        if (bounds == null || bounds.x == 0 || bounds.y == 0) {
-            return null;
-        }
-
-        Matrix rotateMatrix = new Matrix();
-        rotateMatrix.setRotate(rotation);
-        float[] rotatedBounds = new float[] { bounds.x, bounds.y };
-        rotateMatrix.mapPoints(rotatedBounds);
-        rotatedBounds[0] = Math.abs(rotatedBounds[0]);
-        rotatedBounds[1] = Math.abs(rotatedBounds[1]);
-
-        RectF cropRect = Utils.getMaxCropRect(
-                (int) rotatedBounds[0], (int) rotatedBounds[1], width, height, leftAligned);
-        cropTask.setCropBounds(cropRect);
-
-        if (cropTask.cropBitmap()) {
-            return cropTask.getCroppedBitmap();
-        } else {
-            return null;
-        }
-    }
-
     private void addTemporaryWallpaperTile(final Uri uri, boolean fromRestore) {
-        mTempWallpaperTiles.add(uri);
-        // Add a tile for the image picked from Gallery
-        final FrameLayout pickedImageThumbnail = (FrameLayout) getLayoutInflater().
-                inflate(R.layout.wallpaper_picker_item, mWallpapersView, false);
-        pickedImageThumbnail.setVisibility(View.GONE);
-        mWallpapersView.addView(pickedImageThumbnail, 0);
 
-        // Load the thumbnail
-        final ImageView image = (ImageView) pickedImageThumbnail.findViewById(R.id.wallpaper_image);
-        final Point defaultSize = getDefaultThumbnailSize(this.getResources());
-        final Context context = getContext();
-        new AsyncTask<Void, Bitmap, Bitmap>() {
-            protected Bitmap doInBackground(Void...args) {
-                try {
-                    int rotation = BitmapUtils.getRotationFromExif(context, uri);
-                    return createThumbnail(defaultSize, context, uri, null, null, 0, rotation, false);
-                } catch (SecurityException securityException) {
-                    if (isActivityDestroyed()) {
-                        // Temporarily granted permissions are revoked when the activity
-                        // finishes, potentially resulting in a SecurityException here.
-                        // Even though {@link #isDestroyed} might also return true in different
-                        // situations where the configuration changes, we are fine with
-                        // catching these cases here as well.
-                        cancel(false);
-                    } else {
-                        // otherwise it had a different cause and we throw it further
-                        throw securityException;
-                    }
-                    return null;
-                }
+        // Add a tile for the image picked from Gallery, reusing the existing tile if there is one.
+        View imageTile = null;
+        int indexOfExistingTile = 0;
+        for (; indexOfExistingTile < mWallpapersView.getChildCount(); indexOfExistingTile++) {
+            View thumbnail = mWallpapersView.getChildAt(indexOfExistingTile);
+            Object tag = thumbnail.getTag();
+            if (tag instanceof UriWallpaperInfo && ((UriWallpaperInfo) tag).mUri.equals(uri)) {
+                imageTile = thumbnail;
+                break;
             }
-            protected void onPostExecute(Bitmap thumb) {
-                if (!isCancelled() && thumb != null) {
-                    image.setImageBitmap(thumb);
-                    Drawable thumbDrawable = image.getDrawable();
-                    thumbDrawable.setDither(true);
-                    pickedImageThumbnail.setVisibility(View.VISIBLE);
-                } else {
-                    Log.e(TAG, "Error loading thumbnail for uri=" + uri);
-                }
-            }
-        }.execute();
-
-        UriWallpaperInfo info = new UriWallpaperInfo(uri);
-        pickedImageThumbnail.setTag(info);
-        info.setView(pickedImageThumbnail);
-        addLongPressHandler(pickedImageThumbnail);
-        updateTileIndices();
-        pickedImageThumbnail.setOnClickListener(mThumbnailOnClickListener);
-        if (!fromRestore) {
-            mThumbnailOnClickListener.onClick(pickedImageThumbnail);
         }
+        final UriWallpaperInfo info;
+        if (imageTile != null) {
+            // Always move the existing wallpaper to the front so user can see it without scrolling.
+            mWallpapersView.removeViewAt(indexOfExistingTile);
+            info = (UriWallpaperInfo) imageTile.getTag();
+        } else {
+            // This is the first time this temporary wallpaper has been added
+            info = new UriWallpaperInfo(uri);
+            imageTile = createTileView(mWallpapersView, info, true);
+            mTempWallpaperTiles.add(uri);
+        }
+        mWallpapersView.addView(imageTile, 0);
+        info.loadThumbnaleAsync(this);
+
+        updateTileIndices();
+        if (!fromRestore) {
+            onClick(imageTile);
+        }
+    }
+
+    @Thunk void populateWallpapers(ViewGroup parent, List<? extends WallpaperTileInfo> wallpapers,
+            boolean addLongPressHandler) {
+        for (WallpaperTileInfo info : wallpapers) {
+            parent.addView(createTileView(parent, info, addLongPressHandler));
+        }
+    }
+
+    private View createTileView(ViewGroup parent, WallpaperTileInfo info, boolean addLongPress) {
+        View view = info.createView(this, getLayoutInflater(), parent);
+        view.setTag(info);
+
+        if (addLongPress) {
+            view.setOnLongClickListener(this);
+
+            // Enable stylus button to also trigger long click.
+            final StylusEventHelper stylusEventHelper =
+                    new StylusEventHelper(new SimpleOnStylusPressListener(view), view);
+            view.setOnTouchListener(new View.OnTouchListener() {
+                @SuppressLint("ClickableViewAccessibility")
+                @Override
+                public boolean onTouch(View view, MotionEvent event) {
+                    return stylusEventHelper.onMotionEvent(event);
+                }
+            });
+        }
+        view.setOnClickListener(this);
+        return view;
     }
 
     public void onActivityResult(int requestCode, int resultCode, Intent data) {
@@ -909,19 +446,6 @@
         }
     }
 
-    private void addLongPressHandler(View v) {
-        v.setOnLongClickListener(mLongClickListener);
-
-        // Enable stylus button to also trigger long click.
-        final StylusEventHelper stylusEventHelper = new StylusEventHelper(v);
-        v.setOnTouchListener(new View.OnTouchListener() {
-            @Override
-            public boolean onTouch(View view, MotionEvent event) {
-                return stylusEventHelper.checkAndPerformStylusEvent(event);
-            }
-        });
-    }
-
     private ArrayList<WallpaperTileInfo> findBundledWallpapers() {
         final PackageManager pm = getContext().getPackageManager();
         final ArrayList<WallpaperTileInfo> bundled = new ArrayList<WallpaperTileInfo>(24);
@@ -958,7 +482,8 @@
                     File thumbnail = new File(systemDir, name + "_small" + extension);
                     Bitmap thumb = BitmapFactory.decodeFile(thumbnail.getAbsolutePath());
                     if (thumb != null) {
-                        bundled.add(new FileWallpaperInfo(file, new BitmapDrawable(thumb)));
+                        bundled.add(new FileWallpaperInfo(
+                                file, new BitmapDrawable(getResources(), thumb)));
                     }
                 }
             }
@@ -976,8 +501,7 @@
 
         if (partner == null || !partner.hideDefaultWallpaper()) {
             // Add an entry for the default wallpaper (stored in system resources)
-            WallpaperTileInfo defaultWallpaperInfo = Utilities.ATLEAST_KITKAT
-                    ? getDefaultWallpaper() : getPreKKDefaultWallpaperInfo();
+            WallpaperTileInfo defaultWallpaperInfo = DefaultWallpaperInfo.get(this);
             if (defaultWallpaperInfo != null) {
                 bundled.add(0, defaultWallpaperInfo);
             }
@@ -985,95 +509,6 @@
         return bundled;
     }
 
-    private boolean writeImageToFileAsJpeg(File f, Bitmap b) {
-        try {
-            f.createNewFile();
-            FileOutputStream thumbFileStream =
-                    getContext().openFileOutput(f.getName(), Context.MODE_PRIVATE);
-            b.compress(Bitmap.CompressFormat.JPEG, 95, thumbFileStream);
-            thumbFileStream.close();
-            return true;
-        } catch (IOException e) {
-            Log.e(TAG, "Error while writing bitmap to file " + e);
-            f.delete();
-        }
-        return false;
-    }
-
-    private File getDefaultThumbFile() {
-        return new File(getContext().getFilesDir(), Build.VERSION.SDK_INT
-                + "_" + LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL);
-    }
-
-    private boolean saveDefaultWallpaperThumb(Bitmap b) {
-        // Delete old thumbnails.
-        new File(getContext().getFilesDir(), LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL_OLD).delete();
-        new File(getContext().getFilesDir(), LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL).delete();
-
-        for (int i = Build.VERSION_CODES.JELLY_BEAN; i < Build.VERSION.SDK_INT; i++) {
-            new File(getContext().getFilesDir(), i + "_"
-                    + LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL).delete();
-        }
-        return writeImageToFileAsJpeg(getDefaultThumbFile(), b);
-    }
-
-    private ResourceWallpaperInfo getPreKKDefaultWallpaperInfo() {
-        Resources sysRes = Resources.getSystem();
-        int resId = sysRes.getIdentifier("default_wallpaper", "drawable", "android");
-
-        File defaultThumbFile = getDefaultThumbFile();
-        Bitmap thumb = null;
-        boolean defaultWallpaperExists = false;
-        if (defaultThumbFile.exists()) {
-            thumb = BitmapFactory.decodeFile(defaultThumbFile.getAbsolutePath());
-            defaultWallpaperExists = true;
-        } else {
-            Resources res = getResources();
-            Point defaultThumbSize = getDefaultThumbnailSize(res);
-            int rotation = BitmapUtils.getRotationFromExif(res, resId, this);
-            thumb = createThumbnail(
-                    defaultThumbSize, getContext(), null, null, sysRes, resId, rotation, false);
-            if (thumb != null) {
-                defaultWallpaperExists = saveDefaultWallpaperThumb(thumb);
-            }
-        }
-        if (defaultWallpaperExists) {
-            return new ResourceWallpaperInfo(sysRes, resId, new BitmapDrawable(thumb));
-        }
-        return null;
-    }
-
-    @TargetApi(Build.VERSION_CODES.KITKAT)
-    private DefaultWallpaperInfo getDefaultWallpaper() {
-        File defaultThumbFile = getDefaultThumbFile();
-        Bitmap thumb = null;
-        boolean defaultWallpaperExists = false;
-        if (defaultThumbFile.exists()) {
-            thumb = BitmapFactory.decodeFile(defaultThumbFile.getAbsolutePath());
-            defaultWallpaperExists = true;
-        } else {
-            Resources res = getResources();
-            Point defaultThumbSize = getDefaultThumbnailSize(res);
-            Drawable wallpaperDrawable = WallpaperManager.getInstance(getContext()).getBuiltInDrawable(
-                    defaultThumbSize.x, defaultThumbSize.y, true, 0.5f, 0.5f);
-            if (wallpaperDrawable != null) {
-                thumb = Bitmap.createBitmap(
-                        defaultThumbSize.x, defaultThumbSize.y, Bitmap.Config.ARGB_8888);
-                Canvas c = new Canvas(thumb);
-                wallpaperDrawable.setBounds(0, 0, defaultThumbSize.x, defaultThumbSize.y);
-                wallpaperDrawable.draw(c);
-                c.setBitmap(null);
-            }
-            if (thumb != null) {
-                defaultWallpaperExists = saveDefaultWallpaperThumb(thumb);
-            }
-        }
-        if (defaultWallpaperExists) {
-            return new DefaultWallpaperInfo(new BitmapDrawable(thumb));
-        }
-        return null;
-    }
-
     public Pair<ApplicationInfo, Integer> getWallpaperArrayResourceId() {
         // Context.getPackageName() may return the "original" package name,
         // com.android.launcher3; Resources needs the real package name,
@@ -1108,62 +543,111 @@
         }
     }
 
-    public CropView getCropView() {
-        return mCropView;
-    }
-
     public SavedWallpaperImages getSavedImages() {
         return mSavedImages;
     }
 
-    private static class SimpleWallpapersAdapter extends ArrayAdapter<WallpaperTileInfo> {
-        private final LayoutInflater mLayoutInflater;
-
-        SimpleWallpapersAdapter(Context context, ArrayList<WallpaperTileInfo> wallpapers) {
-            super(context, R.layout.wallpaper_picker_item, wallpapers);
-            mLayoutInflater = LayoutInflater.from(context);
-        }
-
-        public View getView(int position, View convertView, ViewGroup parent) {
-            Drawable thumb = getItem(position).mThumb;
-            if (thumb == null) {
-                Log.e(TAG, "Error decoding thumbnail for wallpaper #" + position);
-            }
-            return createImageTileView(mLayoutInflater, convertView, parent, thumb);
-        }
-    }
-
-    public static View createImageTileView(LayoutInflater layoutInflater,
-            View convertView, ViewGroup parent, Drawable thumb) {
-        View view;
-
-        if (convertView == null) {
-            view = layoutInflater.inflate(R.layout.wallpaper_picker_item, parent, false);
-        } else {
-            view = convertView;
-        }
-
-        ImageView image = (ImageView) view.findViewById(R.id.wallpaper_image);
-
-        if (thumb != null) {
-            image.setImageDrawable(thumb);
-            thumb.setDither(true);
-        }
-
-        return view;
-    }
-
     public void startActivityForResultSafely(Intent intent, int requestCode) {
         Utilities.startActivityForResultSafely(getActivity(), intent, requestCode);
     }
 
     @Override
     public boolean enableRotation() {
-        // Check if rotation is enabled for this device.
-        if (Utilities.isRotationAllowedForDevice(getContext()))
-            return true;
+        return super.enableRotation() ||
+                getContentResolver().call(LauncherSettings.Settings.CONTENT_URI,
+                        LauncherSettings.Settings.METHOD_GET_BOOLEAN,
+                        Utilities.ALLOW_ROTATION_PREFERENCE_KEY, new Bundle())
+                .getBoolean(LauncherSettings.Settings.EXTRA_VALUE);
+    }
 
-        // Check if the user has specifically enabled rotation via preferences.
-        return Utilities.isAllowRotationPrefEnabled(getApplicationContext(), true);
+    // CAB for deleting items
+    /**
+     * Called when the action mode is created; startActionMode() was called
+     */
+    @Override
+    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+        // Inflate a menu resource providing context menu items
+        MenuInflater inflater = mode.getMenuInflater();
+        inflater.inflate(R.menu.cab_delete_wallpapers, menu);
+        return true;
+    }
+
+    /**
+     * Called each time the action mode is shown. Always called after onCreateActionMode,
+     * but may be called multiple times if the mode is invalidated.
+     */
+    @Override
+    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+        int childCount = mWallpapersView.getChildCount();
+        int numCheckedItems = 0;
+        for (int i = 0; i < childCount; i++) {
+            CheckableFrameLayout c = (CheckableFrameLayout) mWallpapersView.getChildAt(i);
+            if (c.isChecked()) {
+                numCheckedItems++;
+            }
+        }
+
+        if (numCheckedItems == 0) {
+            mode.finish();
+            return true;
+        } else {
+            mode.setTitle(getResources().getQuantityString(
+                    R.plurals.number_of_items_selected, numCheckedItems, numCheckedItems));
+            return true;
+        }
+    }
+
+    /**
+     * Called when the user selects a contextual menu item
+     */
+    @Override
+    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+        int itemId = item.getItemId();
+        if (itemId == R.id.menu_delete) {
+            int childCount = mWallpapersView.getChildCount();
+            ArrayList<View> viewsToRemove = new ArrayList<View>();
+            boolean selectedTileRemoved = false;
+            for (int i = 0; i < childCount; i++) {
+                CheckableFrameLayout c =
+                        (CheckableFrameLayout) mWallpapersView.getChildAt(i);
+                if (c.isChecked()) {
+                    WallpaperTileInfo info = (WallpaperTileInfo) c.getTag();
+                    info.onDelete(WallpaperPickerActivity.this);
+                    viewsToRemove.add(c);
+                    if (i == mSelectedIndex) {
+                        selectedTileRemoved = true;
+                    }
+                }
+            }
+            for (View v : viewsToRemove) {
+                mWallpapersView.removeView(v);
+            }
+            if (selectedTileRemoved) {
+                mSelectedIndex = -1;
+                mSelectedTile = null;
+                setSystemWallpaperVisiblity(true);
+            }
+            updateTileIndices();
+            mode.finish(); // Action picked, so close the CAB
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Called when the user exits the action mode
+     */
+    @Override
+    public void onDestroyActionMode(ActionMode mode) {
+        int childCount = mWallpapersView.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            CheckableFrameLayout c = (CheckableFrameLayout) mWallpapersView.getChildAt(i);
+            c.setChecked(false);
+        }
+        if (mSelectedTile != null) {
+            mSelectedTile.setSelected(true);
+        }
+        mActionMode = null;
     }
 }
diff --git a/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/DefaultWallpaperInfo.java b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/DefaultWallpaperInfo.java
new file mode 100644
index 0000000..7ede260
--- /dev/null
+++ b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/DefaultWallpaperInfo.java
@@ -0,0 +1,164 @@
+package com.android.launcher3.wallpapertileinfo;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Point;
+import android.graphics.RectF;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.util.Log;
+
+import com.android.gallery3d.common.BitmapUtils;
+import com.android.launcher3.LauncherFiles;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.WallpaperCropActivity.CropViewScaleAndOffsetProvider;
+import com.android.launcher3.WallpaperPickerActivity;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+public class DefaultWallpaperInfo extends DrawableThumbWallpaperInfo {
+
+    private static final String TAG = "DefaultWallpaperInfo";
+
+    public DefaultWallpaperInfo(Drawable thumb) {
+        super(thumb);
+    }
+
+    @Override
+    public void onClick(WallpaperPickerActivity a) {
+        a.setCropViewTileSource(null, false, false, new CropViewScaleAndOffsetProvider() {
+
+            @Override
+            public float getScale(Point wallpaperSize, RectF crop) {
+                return 1f;
+            }
+
+            @Override
+            public float getParallaxOffset() {
+                return 0;
+            }
+        }, null);
+    }
+
+    @Override
+    public void onSave(WallpaperPickerActivity a) {
+        try {
+            WallpaperManager.getInstance(a.getContext()).clear();
+            a.setResult(Activity.RESULT_OK);
+        } catch (IOException e) {
+            Log.w(TAG, "Setting wallpaper to default threw exception", e);
+        }
+        a.finish();
+    }
+
+    @Override
+    public boolean isSelectable() {
+        return true;
+    }
+
+    @Override
+    public boolean isNamelessWallpaper() {
+        return true;
+    }
+
+    /**
+     * @return the system default wallpaper tile or null
+     */
+    public static WallpaperTileInfo get(Context context) {
+        return Utilities.ATLEAST_KITKAT
+                ? getDefaultWallpaper(context) : getPreKKDefaultWallpaperInfo(context);
+    }
+
+    @TargetApi(Build.VERSION_CODES.KITKAT)
+    private static DefaultWallpaperInfo getDefaultWallpaper(Context context) {
+        File defaultThumbFile = getDefaultThumbFile(context);
+        Bitmap thumb = null;
+        boolean defaultWallpaperExists = false;
+        Resources res = context.getResources();
+
+        if (defaultThumbFile.exists()) {
+            thumb = BitmapFactory.decodeFile(defaultThumbFile.getAbsolutePath());
+            defaultWallpaperExists = true;
+        } else {
+            Point defaultThumbSize = getDefaultThumbSize(res);
+            Drawable wallpaperDrawable = WallpaperManager.getInstance(context).getBuiltInDrawable(
+                    defaultThumbSize.x, defaultThumbSize.y, true, 0.5f, 0.5f);
+            if (wallpaperDrawable != null) {
+                thumb = Bitmap.createBitmap(
+                        defaultThumbSize.x, defaultThumbSize.y, Bitmap.Config.ARGB_8888);
+                Canvas c = new Canvas(thumb);
+                wallpaperDrawable.setBounds(0, 0, defaultThumbSize.x, defaultThumbSize.y);
+                wallpaperDrawable.draw(c);
+                c.setBitmap(null);
+            }
+            if (thumb != null) {
+                defaultWallpaperExists = saveDefaultWallpaperThumb(context, thumb);
+            }
+        }
+        if (defaultWallpaperExists) {
+            return new DefaultWallpaperInfo(new BitmapDrawable(res, thumb));
+        }
+        return null;
+    }
+
+    private static ResourceWallpaperInfo getPreKKDefaultWallpaperInfo(Context context) {
+        Resources sysRes = Resources.getSystem();
+        Resources res = context.getResources();
+
+        int resId = sysRes.getIdentifier("default_wallpaper", "drawable", "android");
+
+        File defaultThumbFile = getDefaultThumbFile(context);
+        Bitmap thumb = null;
+        boolean defaultWallpaperExists = false;
+        if (defaultThumbFile.exists()) {
+            thumb = BitmapFactory.decodeFile(defaultThumbFile.getAbsolutePath());
+            defaultWallpaperExists = true;
+        } else {
+            int rotation = BitmapUtils.getRotationFromExif(res, resId, context);
+            thumb = createThumbnail(context, null, null, sysRes, resId, rotation, false);
+            if (thumb != null) {
+                defaultWallpaperExists = saveDefaultWallpaperThumb(context, thumb);
+            }
+        }
+        if (defaultWallpaperExists) {
+            return new ResourceWallpaperInfo(sysRes, resId, new BitmapDrawable(res, thumb));
+        }
+        return null;
+    }
+
+    private static File getDefaultThumbFile(Context context) {
+        return new File(context.getFilesDir(), Build.VERSION.SDK_INT
+                + "_" + LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL);
+    }
+
+    private static boolean saveDefaultWallpaperThumb(Context c, Bitmap b) {
+        // Delete old thumbnails.
+        new File(c.getFilesDir(), LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL_OLD).delete();
+        new File(c.getFilesDir(), LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL).delete();
+
+        for (int i = Build.VERSION_CODES.JELLY_BEAN; i < Build.VERSION.SDK_INT; i++) {
+            new File(c.getFilesDir(), i + "_" + LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL).delete();
+        }
+        File f = getDefaultThumbFile(c);
+        try {
+            f.createNewFile();
+            FileOutputStream thumbFileStream = c.openFileOutput(f.getName(), Context.MODE_PRIVATE);
+            b.compress(Bitmap.CompressFormat.JPEG, 95, thumbFileStream);
+            thumbFileStream.close();
+            return true;
+        } catch (IOException e) {
+            Log.e(TAG, "Error while writing bitmap to file " + e);
+            f.delete();
+            return false;
+        }
+    }
+}
\ No newline at end of file
diff --git a/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/DrawableThumbWallpaperInfo.java b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/DrawableThumbWallpaperInfo.java
new file mode 100644
index 0000000..a55375d
--- /dev/null
+++ b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/DrawableThumbWallpaperInfo.java
@@ -0,0 +1,37 @@
+package com.android.launcher3.wallpapertileinfo;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import com.android.launcher3.R;
+
+/**
+ * WallpaperTileInfo which uses drawable as the thumbnail.
+ */
+public abstract class DrawableThumbWallpaperInfo extends WallpaperTileInfo {
+
+    private final Drawable mThumb;
+
+    DrawableThumbWallpaperInfo(Drawable thumb) {
+        mThumb = thumb;
+    }
+
+    @Override
+    public View createView(Context context, LayoutInflater inflator, ViewGroup parent) {
+        mView = inflator.inflate(R.layout.wallpaper_picker_item, parent, false);
+        setThumb(mThumb);
+        return mView;
+    }
+
+    public void setThumb(Drawable thumb) {
+        if (mView != null && thumb != null) {
+            thumb.setDither(true);
+            ImageView image = (ImageView) mView.findViewById(R.id.wallpaper_image);
+            image.setImageDrawable(thumb);
+        }
+    }
+}
diff --git a/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/FileWallpaperInfo.java b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/FileWallpaperInfo.java
new file mode 100644
index 0000000..f6a46fc
--- /dev/null
+++ b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/FileWallpaperInfo.java
@@ -0,0 +1,52 @@
+package com.android.launcher3.wallpapertileinfo;
+
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+
+import com.android.launcher3.WallpaperPickerActivity;
+import com.android.photos.BitmapRegionTileSource;
+import com.android.photos.BitmapRegionTileSource.BitmapSource;
+
+import java.io.File;
+
+public class FileWallpaperInfo extends DrawableThumbWallpaperInfo {
+
+    private final File mFile;
+
+    public FileWallpaperInfo(File target, Drawable thumb) {
+        super(thumb);
+        mFile = target;
+    }
+
+    @Override
+    public void onClick(final WallpaperPickerActivity a) {
+        a.setWallpaperButtonEnabled(false);
+        final BitmapRegionTileSource.FilePathBitmapSource bitmapSource =
+                new BitmapRegionTileSource.FilePathBitmapSource(mFile.getAbsolutePath());
+        a.setCropViewTileSource(bitmapSource, false, true, null, new Runnable() {
+
+            @Override
+            public void run() {
+                if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) {
+                    a.setWallpaperButtonEnabled(true);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void onSave(WallpaperPickerActivity a) {
+        boolean shouldFadeOutOnFinish = a.getWallpaperParallaxOffset() == 0f;
+        a.setWallpaper(Uri.fromFile(mFile), shouldFadeOutOnFinish);
+    }
+
+    @Override
+    public boolean isSelectable() {
+        return true;
+    }
+
+    @Override
+    public boolean isNamelessWallpaper() {
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/LiveWallpaperInfo.java b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/LiveWallpaperInfo.java
new file mode 100644
index 0000000..d800ba6
--- /dev/null
+++ b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/LiveWallpaperInfo.java
@@ -0,0 +1,118 @@
+package com.android.launcher3.wallpapertileinfo;
+
+import android.app.WallpaperInfo;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.service.wallpaper.WallpaperService;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.launcher3.R;
+import com.android.launcher3.WallpaperPickerActivity;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public class LiveWallpaperInfo extends WallpaperTileInfo {
+
+    private static final String TAG = "LiveWallpaperTile";
+
+    private Drawable mThumbnail;
+    private WallpaperInfo mInfo;
+
+    public LiveWallpaperInfo(Drawable thumbnail, WallpaperInfo info, Intent intent) {
+        mThumbnail = thumbnail;
+        mInfo = info;
+    }
+
+    @Override
+    public void onClick(WallpaperPickerActivity a) {
+        Intent preview = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);
+        preview.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT,
+                mInfo.getComponent());
+        a.startActivityForResultSafely(preview,
+                WallpaperPickerActivity.PICK_WALLPAPER_THIRD_PARTY_ACTIVITY);
+    }
+
+    @Override
+    public View createView(Context context, LayoutInflater inflator, ViewGroup parent) {
+        mView = inflator.inflate(R.layout.wallpaper_picker_live_wallpaper_item, parent, false);
+
+        ImageView image = (ImageView) mView.findViewById(R.id.wallpaper_image);
+        ImageView icon = (ImageView) mView.findViewById(R.id.wallpaper_icon);
+        if (mThumbnail != null) {
+            image.setImageDrawable(mThumbnail);
+            icon.setVisibility(View.GONE);
+        } else {
+            icon.setImageDrawable(mInfo.loadIcon(context.getPackageManager()));
+            icon.setVisibility(View.VISIBLE);
+        }
+
+        TextView label = (TextView) mView.findViewById(R.id.wallpaper_item_label);
+        label.setText(mInfo.loadLabel(context.getPackageManager()));
+        return mView;
+    }
+
+    /**
+     * An async task to load various live wallpaper tiles.
+     */
+    public static class LoaderTask extends AsyncTask<Void, Void, List<LiveWallpaperInfo>> {
+        private final Context mContext;
+
+        public LoaderTask(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        protected List<LiveWallpaperInfo> doInBackground(Void... params) {
+            final PackageManager pm = mContext.getPackageManager();
+
+            List<ResolveInfo> list = pm.queryIntentServices(
+                    new Intent(WallpaperService.SERVICE_INTERFACE),
+                    PackageManager.GET_META_DATA);
+
+            Collections.sort(list, new Comparator<ResolveInfo>() {
+                final Collator mCollator = Collator.getInstance();
+
+                public int compare(ResolveInfo info1, ResolveInfo info2) {
+                    return mCollator.compare(info1.loadLabel(pm), info2.loadLabel(pm));
+                }
+            });
+
+            List<LiveWallpaperInfo> result = new ArrayList<>();
+
+            for (ResolveInfo resolveInfo : list) {
+                WallpaperInfo info = null;
+                try {
+                    info = new WallpaperInfo(mContext, resolveInfo);
+                } catch (XmlPullParserException | IOException e) {
+                    Log.w(TAG, "Skipping wallpaper " + resolveInfo.serviceInfo, e);
+                    continue;
+                }
+
+
+                Drawable thumb = info.loadThumbnail(pm);
+                Intent launchIntent = new Intent(WallpaperService.SERVICE_INTERFACE);
+                launchIntent.setClassName(info.getPackageName(), info.getServiceName());
+                result.add(new LiveWallpaperInfo(thumb, info, launchIntent));
+            }
+
+            return result;
+        }
+    }
+}
\ No newline at end of file
diff --git a/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/PickImageInfo.java b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/PickImageInfo.java
new file mode 100644
index 0000000..9d8cc1c
--- /dev/null
+++ b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/PickImageInfo.java
@@ -0,0 +1,74 @@
+package com.android.launcher3.wallpapertileinfo;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.PorterDuff;
+import android.os.Process;
+import android.provider.MediaStore;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import com.android.launcher3.R;
+import com.android.launcher3.WallpaperPickerActivity;
+
+public class PickImageInfo extends WallpaperTileInfo {
+
+    @Override
+    public void onClick(WallpaperPickerActivity a) {
+        Intent intent = new Intent(Intent.ACTION_GET_CONTENT).setType("image/*");
+        a.startActivityForResultSafely(intent, WallpaperPickerActivity.IMAGE_PICK);
+    }
+
+    @Override
+    public View createView(Context context, LayoutInflater inflator, ViewGroup parent) {
+        mView = inflator.inflate(R.layout.wallpaper_picker_image_picker_item, parent, false);
+
+        // Make its background the last photo taken on external storage
+        Bitmap lastPhoto = getThumbnailOfLastPhoto(context);
+        if (lastPhoto != null) {
+            ImageView galleryThumbnailBg =
+                    (ImageView) mView.findViewById(R.id.wallpaper_image);
+            galleryThumbnailBg.setImageBitmap(lastPhoto);
+            int colorOverlay = context.getResources().getColor(R.color.wallpaper_picker_translucent_gray);
+            galleryThumbnailBg.setColorFilter(colorOverlay, PorterDuff.Mode.SRC_ATOP);
+        }
+
+        mView.setTag(this);
+        return mView;
+    }
+
+    private Bitmap getThumbnailOfLastPhoto(Context context) {
+        boolean canReadExternalStorage = context.checkPermission(
+                Manifest.permission.READ_EXTERNAL_STORAGE, Process.myPid(), Process.myUid()) ==
+                PackageManager.PERMISSION_GRANTED;
+
+        if (!canReadExternalStorage) {
+            // MediaStore.Images.Media.EXTERNAL_CONTENT_URI requires
+            // the READ_EXTERNAL_STORAGE permission
+            return null;
+        }
+
+        Cursor cursor = MediaStore.Images.Media.query(context.getContentResolver(),
+                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+                new String[] { MediaStore.Images.ImageColumns._ID,
+                    MediaStore.Images.ImageColumns.DATE_TAKEN},
+                null, null, MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC LIMIT 1");
+
+        Bitmap thumb = null;
+        if (cursor != null) {
+            if (cursor.moveToNext()) {
+                int id = cursor.getInt(0);
+                thumb = MediaStore.Images.Thumbnails.getThumbnail(context.getContentResolver(),
+                        id, MediaStore.Images.Thumbnails.MINI_KIND, null);
+            }
+            cursor.close();
+        }
+        return thumb;
+    }
+}
\ No newline at end of file
diff --git a/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/ResourceWallpaperInfo.java b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/ResourceWallpaperInfo.java
new file mode 100644
index 0000000..d63714c
--- /dev/null
+++ b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/ResourceWallpaperInfo.java
@@ -0,0 +1,65 @@
+package com.android.launcher3.wallpapertileinfo;
+
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+
+import com.android.launcher3.WallpaperCropActivity.CropViewScaleAndOffsetProvider;
+import com.android.launcher3.WallpaperPickerActivity;
+import com.android.photos.BitmapRegionTileSource;
+import com.android.photos.BitmapRegionTileSource.BitmapSource;
+
+public class ResourceWallpaperInfo extends DrawableThumbWallpaperInfo {
+
+    private final Resources mResources;
+    private final int mResId;
+
+    public ResourceWallpaperInfo(Resources res, int resId, Drawable thumb) {
+        super(thumb);
+        mResources = res;
+        mResId = resId;
+    }
+
+    @Override
+    public void onClick(final WallpaperPickerActivity a) {
+        a.setWallpaperButtonEnabled(false);
+        final BitmapRegionTileSource.ResourceBitmapSource bitmapSource =
+                new BitmapRegionTileSource.ResourceBitmapSource(mResources, mResId, a);
+        a.setCropViewTileSource(bitmapSource, false, false, new CropViewScaleAndOffsetProvider() {
+
+            @Override
+            public float getScale(Point wallpaperSize, RectF crop) {
+                return wallpaperSize.x /crop.width();
+            }
+
+            @Override
+            public float getParallaxOffset() {
+                return a.getWallpaperParallaxOffset();
+            }
+        }, new Runnable() {
+
+            @Override
+            public void run() {
+                if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) {
+                    a.setWallpaperButtonEnabled(true);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void onSave(WallpaperPickerActivity a) {
+        a.cropImageAndSetWallpaper(mResources, mResId, true /* shouldFadeOutOnFinish */);
+    }
+
+    @Override
+    public boolean isSelectable() {
+        return true;
+    }
+
+    @Override
+    public boolean isNamelessWallpaper() {
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/ThirdPartyWallpaperInfo.java b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/ThirdPartyWallpaperInfo.java
new file mode 100644
index 0000000..5e2538f
--- /dev/null
+++ b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/ThirdPartyWallpaperInfo.java
@@ -0,0 +1,78 @@
+package com.android.launcher3.wallpapertileinfo;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.android.launcher3.R;
+import com.android.launcher3.WallpaperPickerActivity;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+public class ThirdPartyWallpaperInfo extends WallpaperTileInfo {
+
+    private final ResolveInfo mResolveInfo;
+    private final int mIconSize;
+
+    public ThirdPartyWallpaperInfo(ResolveInfo resolveInfo, int iconSize) {
+        mResolveInfo = resolveInfo;
+        mIconSize = iconSize;
+    }
+
+    @Override
+    public void onClick(WallpaperPickerActivity a) {
+        final ComponentName itemComponentName = new ComponentName(
+                mResolveInfo.activityInfo.packageName, mResolveInfo.activityInfo.name);
+        Intent launchIntent = new Intent(Intent.ACTION_SET_WALLPAPER)
+            .setComponent(itemComponentName)
+            .putExtra(WallpaperPickerActivity.EXTRA_WALLPAPER_OFFSET,
+                    a.getWallpaperParallaxOffset());
+        a.startActivityForResultSafely(
+                launchIntent, WallpaperPickerActivity.PICK_WALLPAPER_THIRD_PARTY_ACTIVITY);
+    }
+
+    @Override
+    public View createView(Context context, LayoutInflater inflator, ViewGroup parent) {
+        mView = inflator.inflate(R.layout.wallpaper_picker_third_party_item, parent, false);
+
+        TextView label = (TextView) mView.findViewById(R.id.wallpaper_item_label);
+        label.setText(mResolveInfo.loadLabel(context.getPackageManager()));
+        Drawable icon = mResolveInfo.loadIcon(context.getPackageManager());
+        icon.setBounds(new Rect(0, 0, mIconSize, mIconSize));
+        label.setCompoundDrawables(null, icon, null, null);
+        return mView;
+    }
+
+    public static List<ThirdPartyWallpaperInfo> getAll(Context context) {
+        ArrayList<ThirdPartyWallpaperInfo> result = new ArrayList<>();
+        int iconSize = context.getResources().getDimensionPixelSize(R.dimen.wallpaperItemIconSize);
+
+        final PackageManager pm = context.getPackageManager();
+        Intent pickImageIntent = new Intent(Intent.ACTION_GET_CONTENT).setType("image/*");
+        HashSet<String> excludePackages = new HashSet<>();
+        // Exclude packages which contain an image picker
+        for (ResolveInfo info : pm.queryIntentActivities(pickImageIntent, 0)) {
+            excludePackages.add(info.activityInfo.packageName);
+        }
+        excludePackages.add(context.getPackageName());
+        excludePackages.add("com.android.wallpaper.livepicker");
+
+        final Intent pickWallpaperIntent = new Intent(Intent.ACTION_SET_WALLPAPER);
+        for (ResolveInfo info : pm.queryIntentActivities(pickWallpaperIntent, 0)) {
+            if (!excludePackages.contains(info.activityInfo.packageName)) {
+                result.add(new ThirdPartyWallpaperInfo(info, iconSize));
+            }
+        }
+        return result;
+    }
+}
\ No newline at end of file
diff --git a/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/UriWallpaperInfo.java b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/UriWallpaperInfo.java
new file mode 100644
index 0000000..180eb93
--- /dev/null
+++ b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/UriWallpaperInfo.java
@@ -0,0 +1,109 @@
+package com.android.launcher3.wallpapertileinfo;
+
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Toast;
+
+import com.android.gallery3d.common.BitmapCropTask;
+import com.android.gallery3d.common.BitmapUtils;
+import com.android.launcher3.R;
+import com.android.launcher3.WallpaperPickerActivity;
+import com.android.photos.BitmapRegionTileSource;
+import com.android.photos.BitmapRegionTileSource.BitmapSource;
+
+public class UriWallpaperInfo extends DrawableThumbWallpaperInfo {
+
+    private static final String TAG = "UriWallpaperInfo";
+
+    public final Uri mUri;
+
+    public UriWallpaperInfo(Uri uri) {
+        super(null);
+        mUri = uri;
+    }
+
+    @Override
+    public void onClick(final WallpaperPickerActivity a) {
+        a.setWallpaperButtonEnabled(false);
+        final BitmapRegionTileSource.UriBitmapSource bitmapSource =
+                new BitmapRegionTileSource.UriBitmapSource(a.getContext(), mUri);
+        a.setCropViewTileSource(bitmapSource, true, false, null, new Runnable() {
+
+            @Override
+            public void run() {
+                if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) {
+                    a.selectTile(mView);
+                    a.setWallpaperButtonEnabled(true);
+                } else {
+                    ViewGroup parent = (ViewGroup) mView.getParent();
+                    if (parent != null) {
+                        parent.removeView(mView);
+                        Toast.makeText(a.getContext(), R.string.image_load_fail,
+                                Toast.LENGTH_SHORT).show();
+                    }
+                }
+            }
+        });
+    }
+
+    @Override
+    public void onSave(final WallpaperPickerActivity a) {
+        BitmapCropTask.OnBitmapCroppedHandler h = new BitmapCropTask.OnBitmapCroppedHandler() {
+            public void onBitmapCropped(byte[] imageBytes) {
+                // rotation is set to 0 since imageBytes has already been correctly rotated
+                Bitmap thumb = createThumbnail(a, null, imageBytes, null, 0, 0, true);
+                a.getSavedImages().writeImage(thumb, imageBytes);
+            }
+        };
+        boolean shouldFadeOutOnFinish = a.getWallpaperParallaxOffset() == 0f;
+        a.cropImageAndSetWallpaper(mUri, h, shouldFadeOutOnFinish);
+    }
+
+    @Override
+    public boolean isSelectable() {
+        return true;
+    }
+
+    @Override
+    public boolean isNamelessWallpaper() {
+        return true;
+    }
+
+    public void loadThumbnaleAsync(final WallpaperPickerActivity activity) {
+        mView.setVisibility(View.GONE);
+        new AsyncTask<Void, Void, Bitmap>() {
+            protected Bitmap doInBackground(Void...args) {
+                try {
+                    int rotation = BitmapUtils.getRotationFromExif(activity, mUri);
+                    return createThumbnail(activity, mUri, null, null, 0, rotation, false);
+                } catch (SecurityException securityException) {
+                    if (activity.isActivityDestroyed()) {
+                        // Temporarily granted permissions are revoked when the activity
+                        // finishes, potentially resulting in a SecurityException here.
+                        // Even though {@link #isDestroyed} might also return true in different
+                        // situations where the configuration changes, we are fine with
+                        // catching these cases here as well.
+                        cancel(false);
+                    } else {
+                        // otherwise it had a different cause and we throw it further
+                        throw securityException;
+                    }
+                    return null;
+                }
+            }
+            protected void onPostExecute(Bitmap thumb) {
+                if (!isCancelled() && thumb != null) {
+                    setThumb(new BitmapDrawable(activity.getResources(), thumb));
+                    mView.setVisibility(View.VISIBLE);
+                } else {
+                    Log.e(TAG, "Error loading thumbnail for uri=" + mUri);
+                }
+            }
+        }.execute();
+    }
+}
\ No newline at end of file
diff --git a/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/WallpaperTileInfo.java b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/WallpaperTileInfo.java
new file mode 100644
index 0000000..5fc317c
--- /dev/null
+++ b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/WallpaperTileInfo.java
@@ -0,0 +1,87 @@
+package com.android.launcher3.wallpapertileinfo;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.RectF;
+import android.net.Uri;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.gallery3d.common.BitmapCropTask;
+import com.android.gallery3d.common.Utils;
+import com.android.launcher3.R;
+import com.android.launcher3.WallpaperPickerActivity;
+
+public abstract class WallpaperTileInfo {
+
+    protected View mView;
+
+    public void onClick(WallpaperPickerActivity a) {}
+
+    public void onSave(WallpaperPickerActivity a) {}
+
+    public void onDelete(WallpaperPickerActivity a) {}
+
+    public boolean isSelectable() { return false; }
+
+    public boolean isNamelessWallpaper() { return false; }
+
+    public void onIndexUpdated(CharSequence label) {
+        if (isNamelessWallpaper()) {
+            mView.setContentDescription(label);
+        }
+    }
+
+    public abstract View createView(Context context, LayoutInflater inflator, ViewGroup parent);
+
+    protected static Point getDefaultThumbSize(Resources res) {
+        return new Point(res.getDimensionPixelSize(R.dimen.wallpaperThumbnailWidth),
+                res.getDimensionPixelSize(R.dimen.wallpaperThumbnailHeight));
+
+    }
+
+    protected static Bitmap createThumbnail(Context context, Uri uri, byte[] imageBytes,
+            Resources res, int resId, int rotation, boolean leftAligned) {
+        Point size = getDefaultThumbSize(context.getResources());
+        int width = size.x;
+        int height = size.y;
+
+        BitmapCropTask cropTask;
+        if (uri != null) {
+            cropTask = new BitmapCropTask(
+                    context, uri, null, rotation, width, height, false, true, null);
+        } else if (imageBytes != null) {
+            cropTask = new BitmapCropTask(
+                    imageBytes, null, rotation, width, height, false, true, null);
+        }  else {
+            cropTask = new BitmapCropTask(
+                    context, res, resId, null, rotation, width, height, false, true, null);
+        }
+        Point bounds = cropTask.getImageBounds();
+        if (bounds == null || bounds.x == 0 || bounds.y == 0) {
+            return null;
+        }
+
+        Matrix rotateMatrix = new Matrix();
+        rotateMatrix.setRotate(rotation);
+        float[] rotatedBounds = new float[] { bounds.x, bounds.y };
+        rotateMatrix.mapPoints(rotatedBounds);
+        rotatedBounds[0] = Math.abs(rotatedBounds[0]);
+        rotatedBounds[1] = Math.abs(rotatedBounds[1]);
+
+        RectF cropRect = Utils.getMaxCropRect(
+                (int) rotatedBounds[0], (int) rotatedBounds[1], width, height, leftAligned);
+        cropTask.setCropBounds(cropRect);
+
+        if (cropTask.cropBitmap()) {
+            return cropTask.getCroppedBitmap();
+        } else {
+            return null;
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java b/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java
index 2f367bb..2f9c9a3 100644
--- a/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java
+++ b/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java
@@ -25,7 +25,6 @@
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Rect;
-import android.media.ExifInterface;
 import android.net.Uri;
 import android.opengl.GLUtils;
 import android.os.Build;
diff --git a/WallpaperPicker/src/com/android/photos/views/TiledImageView.java b/WallpaperPicker/src/com/android/photos/views/TiledImageView.java
index 7e3e1a9..6f7a530 100644
--- a/WallpaperPicker/src/com/android/photos/views/TiledImageView.java
+++ b/WallpaperPicker/src/com/android/photos/views/TiledImageView.java
@@ -17,12 +17,7 @@
 package com.android.photos.views;
 
 import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
 import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Paint.Align;
 import android.graphics.RectF;
 import android.opengl.GLSurfaceView;
 import android.opengl.GLSurfaceView.Renderer;
@@ -82,7 +77,6 @@
         mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
         addView(mGLSurfaceView, new LayoutParams(
                 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
-        //setTileSource(new ColoredTiles());
     }
 
     @Override
@@ -247,66 +241,4 @@
         }
 
     }
-
-    @SuppressWarnings("unused")
-    private static class ColoredTiles implements TileSource {
-        private static final int[] COLORS = new int[] {
-            Color.RED,
-            Color.BLUE,
-            Color.YELLOW,
-            Color.GREEN,
-            Color.CYAN,
-            Color.MAGENTA,
-            Color.WHITE,
-        };
-
-        private Paint mPaint = new Paint();
-        private Canvas mCanvas = new Canvas();
-
-        @Override
-        public int getTileSize() {
-            return 256;
-        }
-
-        @Override
-        public int getImageWidth() {
-            return 16384;
-        }
-
-        @Override
-        public int getImageHeight() {
-            return 8192;
-        }
-
-        @Override
-        public int getRotation() {
-            return 0;
-        }
-
-        @Override
-        public Bitmap getTile(int level, int x, int y, Bitmap bitmap) {
-            int tileSize = getTileSize();
-            if (bitmap == null) {
-                bitmap = Bitmap.createBitmap(tileSize, tileSize,
-                        Bitmap.Config.ARGB_8888);
-            }
-            mCanvas.setBitmap(bitmap);
-            mCanvas.drawColor(COLORS[level]);
-            mPaint.setColor(Color.BLACK);
-            mPaint.setTextSize(20);
-            mPaint.setTextAlign(Align.CENTER);
-            mCanvas.drawText(x + "x" + y, 128, 128, mPaint);
-            tileSize <<= level;
-            x /= tileSize;
-            y /= tileSize;
-            mCanvas.drawText(x + "x" + y + " @ " + level, 128, 30, mPaint);
-            mCanvas.setBitmap(null);
-            return bitmap;
-        }
-
-        @Override
-        public BasicTexture getPreview() {
-            return null;
-        }
-    }
 }
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..d971755
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,55 @@
+buildscript {
+    repositories {
+        mavenCentral()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:1.3.0'
+        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.7.0'
+    }
+}
+
+apply plugin: 'com.android.application'
+apply plugin: 'com.google.protobuf'
+
+android {
+    compileSdkVersion 23
+    buildToolsVersion "22.0.1"
+
+    defaultConfig {
+        applicationId "com.android.launcher3"
+        minSdkVersion 16
+        targetSdkVersion 23
+        versionCode 1
+        versionName "1.0"
+    }
+    buildTypes {
+        debug {
+            minifyEnabled false
+        }
+    }
+    sourceSets {
+        main {
+            res.srcDirs = ['res', 'WallpaperPicker/res']
+            main.java.srcDirs = ['src', 'WallpaperPicker/src']
+            manifest.srcFile 'AndroidManifest.xml'
+            proto.srcDirs 'protos/'
+        }
+    }
+}
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    compile 'com.android.support:support-v4:+'
+    compile 'com.android.support:recyclerview-v7:+'
+    compile 'com.google.protobuf.nano:protobuf-javanano:3.0.0-alpha-2'
+}
+
+protobuf {
+    // Configure the protoc executable
+    protoc {
+        artifact = 'com.google.protobuf:protoc:3.0.0-alpha-3'
+    }
+}
diff --git a/print_db.py b/print_db.py
index 05237d0..7257a12 100755
--- a/print_db.py
+++ b/print_db.py
@@ -4,6 +4,7 @@
 import codecs
 import os
 import pprint
+import re
 import shutil
 import sys
 import sqlite3
@@ -22,9 +23,10 @@
 INDEX_FILE = DIR + "/index.html"
 
 def usage():
-  print "usage: print_db.py launcher.db <sw600|sw720> -- prints a launcher.db"
-  print "usage: print_db.py <sw600|sw720> -- adb pulls a launcher.db from a device"
-  print "       and prints it"
+  print "usage: print_db.py launcher.db <4x4|5x5|5x6|...> -- prints a launcher.db with"
+  print "       the specified grid size (rows x cols)"
+  print "usage: print_db.py <4x4|5x5|5x6|...> -- adb pulls a launcher.db from a device"
+  print "       and prints it with the specified grid size (rows x cols)"
   print
   print "The dump will be created in a directory called db_files in cwd."
   print "This script will delete any db_files directory you have now"
@@ -41,7 +43,7 @@
 def pull_file(fn):
   print "pull_file: " + fn
   rv = os.system("adb pull"
-    + " /data/data/com.google.android.googlequicksearchbox/databases/launcher.db"
+    + " /data/data/com.android.launcher3/databases/launcher.db"
     + " " + fn);
   if rv != 0:
     print "adb pull failed"
@@ -287,16 +289,11 @@
 
 def updateDeviceClassConstants(str):
   global SCREENS, COLUMNS, ROWS, HOTSEAT_SIZE
-  devClass = str.lower()
-  if devClass == "sw600":
-    COLUMNS = 6
-    ROWS = 6
-    HOTSEAT_SIZE = 6
-    return True
-  elif devClass == "sw720":
-    COLUMNS = 8
-    ROWS = 6
-    HOTSEAT_SIZE = 8
+  match = re.search(r"(\d+)x(\d+)", str)
+  if match:
+    COLUMNS = int(match.group(1))
+    ROWS = int(match.group(2))
+    HOTSEAT_SIZE = 2 * int(COLUMNS / 2)
     return True
   return False
 
diff --git a/proguard.flags b/proguard.flags
index a8e2b60..22ffa3c 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -2,11 +2,16 @@
   *;
 }
 
+-keep class com.android.launcher3.allapps.AllAppsBackgroundDrawable {
+  public void setAlpha(int);
+  public int getAlpha();
+}
+
 -keep class com.android.launcher3.BaseRecyclerViewFastScrollBar {
-  public void setWidth(int);
-  public int getWidth();
-  public void setTrackAlpha(int);
-  public int getTrackAlpha();
+  public void setThumbWidth(int);
+  public int getThumbWidth();
+  public void setTrackWidth(int);
+  public int getTrackWidth();
 }
 
 -keep class com.android.launcher3.BaseRecyclerViewFastScrollPopup {
@@ -39,7 +44,7 @@
   public int getY();
 }
 
--keep class com.android.launcher3.DragLayer$LayoutParams {
+-keep class com.android.launcher3.dragndrop.DragLayer$LayoutParams {
   public void setWidth(int);
   public int getWidth();
   public void setHeight(int);
diff --git a/res/animator-v21/overview_button_anim.xml b/res/animator-v21/overview_button_anim.xml
new file mode 100644
index 0000000..aac3d26
--- /dev/null
+++ b/res/animator-v21/overview_button_anim.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2015, 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_pressed="true">
+        <objectAnimator
+            android:duration="@android:integer/config_shortAnimTime"
+            android:propertyName="alpha"
+            android:valueTo="0.5"
+            android:valueType="floatType" />
+    </item>
+
+    <item android:state_focused="true">
+        <objectAnimator
+            android:duration="@android:integer/config_shortAnimTime"
+            android:propertyName="alpha"
+            android:valueTo="0.5"
+            android:valueType="floatType" />
+    </item>
+    <item>
+        <objectAnimator
+            android:duration="@android:integer/config_shortAnimTime"
+            android:propertyName="alpha"
+            android:valueTo="1"
+            android:valueType="floatType" />
+    </item>
+
+</selector>
\ No newline at end of file
diff --git a/res/drawable-hdpi/cling_bg.9.png b/res/drawable-hdpi/cling_bg.9.png
index e173ba5..fb101f4 100644
--- a/res/drawable-hdpi/cling_bg.9.png
+++ b/res/drawable-hdpi/cling_bg.9.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_all_apps_bg_hand.png b/res/drawable-hdpi/ic_all_apps_bg_hand.png
new file mode 100644
index 0000000..43b1bed
--- /dev/null
+++ b/res/drawable-hdpi/ic_all_apps_bg_hand.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_all_apps_bg_icon_1.png b/res/drawable-hdpi/ic_all_apps_bg_icon_1.png
new file mode 100644
index 0000000..d2c4cc1
--- /dev/null
+++ b/res/drawable-hdpi/ic_all_apps_bg_icon_1.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_all_apps_bg_icon_2.png b/res/drawable-hdpi/ic_all_apps_bg_icon_2.png
new file mode 100644
index 0000000..57b7456
--- /dev/null
+++ b/res/drawable-hdpi/ic_all_apps_bg_icon_2.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_all_apps_bg_icon_3.png b/res/drawable-hdpi/ic_all_apps_bg_icon_3.png
new file mode 100644
index 0000000..54fe70b
--- /dev/null
+++ b/res/drawable-hdpi/ic_all_apps_bg_icon_3.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_all_apps_bg_icon_4.png b/res/drawable-hdpi/ic_all_apps_bg_icon_4.png
new file mode 100644
index 0000000..9c0f777
--- /dev/null
+++ b/res/drawable-hdpi/ic_all_apps_bg_icon_4.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_allapps.png b/res/drawable-hdpi/ic_allapps.png
index b98e65f..253755f 100644
--- a/res/drawable-hdpi/ic_allapps.png
+++ b/res/drawable-hdpi/ic_allapps.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_allapps_pressed.png b/res/drawable-hdpi/ic_allapps_pressed.png
index b7eaa67..1e644c5 100644
--- a/res/drawable-hdpi/ic_allapps_pressed.png
+++ b/res/drawable-hdpi/ic_allapps_pressed.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_arrow_back_grey.png b/res/drawable-hdpi/ic_arrow_back_grey.png
index c7c0088..7d7bfb1 100755
--- a/res/drawable-hdpi/ic_arrow_back_grey.png
+++ b/res/drawable-hdpi/ic_arrow_back_grey.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_info_launcher.png b/res/drawable-hdpi/ic_info_launcher.png
index ec0cdd1..11162e1 100644
--- a/res/drawable-hdpi/ic_info_launcher.png
+++ b/res/drawable-hdpi/ic_info_launcher.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pageindicator_add.png b/res/drawable-hdpi/ic_pageindicator_add.png
index ab0e5db..6e3f5af 100644
--- a/res/drawable-hdpi/ic_pageindicator_add.png
+++ b/res/drawable-hdpi/ic_pageindicator_add.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pageindicator_current.png b/res/drawable-hdpi/ic_pageindicator_current.png
index 423ca2b..6dbc4f9 100644
--- a/res/drawable-hdpi/ic_pageindicator_current.png
+++ b/res/drawable-hdpi/ic_pageindicator_current.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pageindicator_current_folder.png b/res/drawable-hdpi/ic_pageindicator_current_folder.png
index 43fbb0e..c6c4228 100644
--- a/res/drawable-hdpi/ic_pageindicator_current_folder.png
+++ b/res/drawable-hdpi/ic_pageindicator_current_folder.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pageindicator_default.png b/res/drawable-hdpi/ic_pageindicator_default.png
index 83fa73f..19945a5 100644
--- a/res/drawable-hdpi/ic_pageindicator_default.png
+++ b/res/drawable-hdpi/ic_pageindicator_default.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pageindicator_default_folder.png b/res/drawable-hdpi/ic_pageindicator_default_folder.png
index 55cab1c..4710374 100644
--- a/res/drawable-hdpi/ic_pageindicator_default_folder.png
+++ b/res/drawable-hdpi/ic_pageindicator_default_folder.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_remove_launcher.png b/res/drawable-hdpi/ic_remove_launcher.png
index c081457..ad2b9af 100644
--- a/res/drawable-hdpi/ic_remove_launcher.png
+++ b/res/drawable-hdpi/ic_remove_launcher.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_search_grey.png b/res/drawable-hdpi/ic_search_grey.png
index bd20ba0..bc50a47 100755
--- a/res/drawable-hdpi/ic_search_grey.png
+++ b/res/drawable-hdpi/ic_search_grey.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_setting.png b/res/drawable-hdpi/ic_setting.png
index 1c12a5b..72a9745 100644
--- a/res/drawable-hdpi/ic_setting.png
+++ b/res/drawable-hdpi/ic_setting.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_setting_pressed.png b/res/drawable-hdpi/ic_setting_pressed.png
index d5b5ca2..b86fce1 100644
--- a/res/drawable-hdpi/ic_setting_pressed.png
+++ b/res/drawable-hdpi/ic_setting_pressed.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_uninstall_launcher.png b/res/drawable-hdpi/ic_uninstall_launcher.png
index 3d8f726..426683c 100644
--- a/res/drawable-hdpi/ic_uninstall_launcher.png
+++ b/res/drawable-hdpi/ic_uninstall_launcher.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_wallpaper.png b/res/drawable-hdpi/ic_wallpaper.png
index 34d5943..5936059 100644
--- a/res/drawable-hdpi/ic_wallpaper.png
+++ b/res/drawable-hdpi/ic_wallpaper.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_wallpaper_pressed.png b/res/drawable-hdpi/ic_wallpaper_pressed.png
index 1588ce7..4bb1958 100644
--- a/res/drawable-hdpi/ic_wallpaper_pressed.png
+++ b/res/drawable-hdpi/ic_wallpaper_pressed.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_widget.png b/res/drawable-hdpi/ic_widget.png
index ed7e1ca..172664b 100644
--- a/res/drawable-hdpi/ic_widget.png
+++ b/res/drawable-hdpi/ic_widget.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_widget_pressed.png b/res/drawable-hdpi/ic_widget_pressed.png
index 19d6fed..7f31ab3 100644
--- a/res/drawable-hdpi/ic_widget_pressed.png
+++ b/res/drawable-hdpi/ic_widget_pressed.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_widget_resize_handle.png b/res/drawable-hdpi/ic_widget_resize_handle.png
index 844f3cf..b243aec 100644
--- a/res/drawable-hdpi/ic_widget_resize_handle.png
+++ b/res/drawable-hdpi/ic_widget_resize_handle.png
Binary files differ
diff --git a/res/drawable-hdpi/page_hover_left.9.png b/res/drawable-hdpi/page_hover_left.9.png
deleted file mode 100644
index cc029d8..0000000
--- a/res/drawable-hdpi/page_hover_left.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/page_hover_left_active.9.png b/res/drawable-hdpi/page_hover_left_active.9.png
deleted file mode 100644
index 20c91a0..0000000
--- a/res/drawable-hdpi/page_hover_left_active.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/page_hover_right.9.png b/res/drawable-hdpi/page_hover_right.9.png
deleted file mode 100644
index a42822a..0000000
--- a/res/drawable-hdpi/page_hover_right.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/page_hover_right_active.9.png b/res/drawable-hdpi/page_hover_right_active.9.png
deleted file mode 100644
index 523fafd..0000000
--- a/res/drawable-hdpi/page_hover_right_active.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/portal_ring_inner.png b/res/drawable-hdpi/portal_ring_inner.png
index c29b4aa..65f5af2 100644
--- a/res/drawable-hdpi/portal_ring_inner.png
+++ b/res/drawable-hdpi/portal_ring_inner.png
Binary files differ
diff --git a/res/drawable-hdpi/portal_ring_inner_nolip.png b/res/drawable-hdpi/portal_ring_inner_nolip.png
index e2f06fe..5be25fc 100644
--- a/res/drawable-hdpi/portal_ring_inner_nolip.png
+++ b/res/drawable-hdpi/portal_ring_inner_nolip.png
Binary files differ
diff --git a/res/drawable-hdpi/portal_ring_outer.png b/res/drawable-hdpi/portal_ring_outer.png
index e7b436b..712eeb2 100644
--- a/res/drawable-hdpi/portal_ring_outer.png
+++ b/res/drawable-hdpi/portal_ring_outer.png
Binary files differ
diff --git a/res/drawable-hdpi/portal_ring_rest.png b/res/drawable-hdpi/portal_ring_rest.png
index e3b1339..33cec32 100644
--- a/res/drawable-hdpi/portal_ring_rest.png
+++ b/res/drawable-hdpi/portal_ring_rest.png
Binary files differ
diff --git a/res/drawable-hdpi/quantum_panel_bitmap.9.png b/res/drawable-hdpi/quantum_panel_bitmap.9.png
index c331929..d2aee73 100644
--- a/res/drawable-hdpi/quantum_panel_bitmap.9.png
+++ b/res/drawable-hdpi/quantum_panel_bitmap.9.png
Binary files differ
diff --git a/res/drawable-hdpi/quantum_panel_dark_bitmap.9.png b/res/drawable-hdpi/quantum_panel_dark_bitmap.9.png
index 0145d36..78345b8 100644
--- a/res/drawable-hdpi/quantum_panel_dark_bitmap.9.png
+++ b/res/drawable-hdpi/quantum_panel_dark_bitmap.9.png
Binary files differ
diff --git a/res/drawable-hdpi/screenpanel.9.png b/res/drawable-hdpi/screenpanel.9.png
index f7ae011..5bccd33 100644
--- a/res/drawable-hdpi/screenpanel.9.png
+++ b/res/drawable-hdpi/screenpanel.9.png
Binary files differ
diff --git a/res/drawable-hdpi/screenpanel_hover.9.png b/res/drawable-hdpi/screenpanel_hover.9.png
index ac8e83d..f6b8c62 100644
--- a/res/drawable-hdpi/screenpanel_hover.9.png
+++ b/res/drawable-hdpi/screenpanel_hover.9.png
Binary files differ
diff --git a/res/drawable-hdpi/virtual_preload.9.png b/res/drawable-hdpi/virtual_preload.9.png
index 71e5326..670088f 100644
--- a/res/drawable-hdpi/virtual_preload.9.png
+++ b/res/drawable-hdpi/virtual_preload.9.png
Binary files differ
diff --git a/res/drawable-hdpi/virtual_preload_folder.9.png b/res/drawable-hdpi/virtual_preload_folder.9.png
index ece3226..68e2afe 100644
--- a/res/drawable-hdpi/virtual_preload_folder.9.png
+++ b/res/drawable-hdpi/virtual_preload_folder.9.png
Binary files differ
diff --git a/res/drawable-hdpi/widget_resize_frame.9.png b/res/drawable-hdpi/widget_resize_frame.9.png
index b0a7403..a710932 100644
--- a/res/drawable-hdpi/widget_resize_frame.9.png
+++ b/res/drawable-hdpi/widget_resize_frame.9.png
Binary files differ
diff --git a/res/drawable-hdpi/widget_resize_shadow.9.png b/res/drawable-hdpi/widget_resize_shadow.9.png
index 6e2932d..7cb5214 100644
--- a/res/drawable-hdpi/widget_resize_shadow.9.png
+++ b/res/drawable-hdpi/widget_resize_shadow.9.png
Binary files differ
diff --git a/res/drawable-hdpi/widget_tile.png b/res/drawable-hdpi/widget_tile.png
index 310ff8b..572bf6f 100644
--- a/res/drawable-hdpi/widget_tile.png
+++ b/res/drawable-hdpi/widget_tile.png
Binary files differ
diff --git a/res/drawable-hdpi/workspace_bg.9.png b/res/drawable-hdpi/workspace_bg.9.png
index 5bbfa4f..ff75186 100644
--- a/res/drawable-hdpi/workspace_bg.9.png
+++ b/res/drawable-hdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable-land-hdpi/workspace_bg.9.png b/res/drawable-land-hdpi/workspace_bg.9.png
index 1a58144..eecd6de 100644
--- a/res/drawable-land-hdpi/workspace_bg.9.png
+++ b/res/drawable-land-hdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable-land-mdpi/workspace_bg.9.png b/res/drawable-land-mdpi/workspace_bg.9.png
index a12519e..626f4a4 100644
--- a/res/drawable-land-mdpi/workspace_bg.9.png
+++ b/res/drawable-land-mdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable-land-xhdpi/workspace_bg.9.png b/res/drawable-land-xhdpi/workspace_bg.9.png
index ce41454..60f7d73 100644
--- a/res/drawable-land-xhdpi/workspace_bg.9.png
+++ b/res/drawable-land-xhdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable-land-xxhdpi/workspace_bg.9.png b/res/drawable-land-xxhdpi/workspace_bg.9.png
index b0b4561..fc71a0f 100644
--- a/res/drawable-land-xxhdpi/workspace_bg.9.png
+++ b/res/drawable-land-xxhdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable-mdpi/cling_bg.9.png b/res/drawable-mdpi/cling_bg.9.png
index fc49c89..6384f29 100644
--- a/res/drawable-mdpi/cling_bg.9.png
+++ b/res/drawable-mdpi/cling_bg.9.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_all_apps_bg_hand.png b/res/drawable-mdpi/ic_all_apps_bg_hand.png
new file mode 100644
index 0000000..8868d6b
--- /dev/null
+++ b/res/drawable-mdpi/ic_all_apps_bg_hand.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_all_apps_bg_icon_1.png b/res/drawable-mdpi/ic_all_apps_bg_icon_1.png
new file mode 100644
index 0000000..4c78288
--- /dev/null
+++ b/res/drawable-mdpi/ic_all_apps_bg_icon_1.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_all_apps_bg_icon_2.png b/res/drawable-mdpi/ic_all_apps_bg_icon_2.png
new file mode 100644
index 0000000..0ed311b
--- /dev/null
+++ b/res/drawable-mdpi/ic_all_apps_bg_icon_2.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_all_apps_bg_icon_3.png b/res/drawable-mdpi/ic_all_apps_bg_icon_3.png
new file mode 100644
index 0000000..2aa3d4e
--- /dev/null
+++ b/res/drawable-mdpi/ic_all_apps_bg_icon_3.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_all_apps_bg_icon_4.png b/res/drawable-mdpi/ic_all_apps_bg_icon_4.png
new file mode 100644
index 0000000..2cdea9c
--- /dev/null
+++ b/res/drawable-mdpi/ic_all_apps_bg_icon_4.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_allapps.png b/res/drawable-mdpi/ic_allapps.png
index f410673..6936b20 100644
--- a/res/drawable-mdpi/ic_allapps.png
+++ b/res/drawable-mdpi/ic_allapps.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_allapps_pressed.png b/res/drawable-mdpi/ic_allapps_pressed.png
index aa4f913..850ded6 100644
--- a/res/drawable-mdpi/ic_allapps_pressed.png
+++ b/res/drawable-mdpi/ic_allapps_pressed.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_arrow_back_grey.png b/res/drawable-mdpi/ic_arrow_back_grey.png
index 5892c77..97999af 100755
--- a/res/drawable-mdpi/ic_arrow_back_grey.png
+++ b/res/drawable-mdpi/ic_arrow_back_grey.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_info_launcher.png b/res/drawable-mdpi/ic_info_launcher.png
index c72d0c2..6fbe5e3 100644
--- a/res/drawable-mdpi/ic_info_launcher.png
+++ b/res/drawable-mdpi/ic_info_launcher.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_pageindicator_add.png b/res/drawable-mdpi/ic_pageindicator_add.png
index 11659a3..d9939b4 100644
--- a/res/drawable-mdpi/ic_pageindicator_add.png
+++ b/res/drawable-mdpi/ic_pageindicator_add.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_pageindicator_current.png b/res/drawable-mdpi/ic_pageindicator_current.png
index ca889c4..832f8ef 100644
--- a/res/drawable-mdpi/ic_pageindicator_current.png
+++ b/res/drawable-mdpi/ic_pageindicator_current.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_pageindicator_current_folder.png b/res/drawable-mdpi/ic_pageindicator_current_folder.png
index 5bbba91..b6c4d7f 100644
--- a/res/drawable-mdpi/ic_pageindicator_current_folder.png
+++ b/res/drawable-mdpi/ic_pageindicator_current_folder.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_pageindicator_default.png b/res/drawable-mdpi/ic_pageindicator_default.png
index 34493b1..9c44afc 100644
--- a/res/drawable-mdpi/ic_pageindicator_default.png
+++ b/res/drawable-mdpi/ic_pageindicator_default.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_pageindicator_default_folder.png b/res/drawable-mdpi/ic_pageindicator_default_folder.png
index 0a987a4..f462558 100644
--- a/res/drawable-mdpi/ic_pageindicator_default_folder.png
+++ b/res/drawable-mdpi/ic_pageindicator_default_folder.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_remove_launcher.png b/res/drawable-mdpi/ic_remove_launcher.png
index 4a2c319..2bb281d 100644
--- a/res/drawable-mdpi/ic_remove_launcher.png
+++ b/res/drawable-mdpi/ic_remove_launcher.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_setting.png b/res/drawable-mdpi/ic_setting.png
index c614e91..60c4fa5 100644
--- a/res/drawable-mdpi/ic_setting.png
+++ b/res/drawable-mdpi/ic_setting.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_setting_pressed.png b/res/drawable-mdpi/ic_setting_pressed.png
index 61e574a..018bea3 100644
--- a/res/drawable-mdpi/ic_setting_pressed.png
+++ b/res/drawable-mdpi/ic_setting_pressed.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_uninstall_launcher.png b/res/drawable-mdpi/ic_uninstall_launcher.png
index af45669..bfcbc6df 100644
--- a/res/drawable-mdpi/ic_uninstall_launcher.png
+++ b/res/drawable-mdpi/ic_uninstall_launcher.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_wallpaper.png b/res/drawable-mdpi/ic_wallpaper.png
index 8f2a00a..a934783 100644
--- a/res/drawable-mdpi/ic_wallpaper.png
+++ b/res/drawable-mdpi/ic_wallpaper.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_wallpaper_pressed.png b/res/drawable-mdpi/ic_wallpaper_pressed.png
index aa598c3..08794d9 100644
--- a/res/drawable-mdpi/ic_wallpaper_pressed.png
+++ b/res/drawable-mdpi/ic_wallpaper_pressed.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_widget.png b/res/drawable-mdpi/ic_widget.png
index 1bd3935..5545350 100644
--- a/res/drawable-mdpi/ic_widget.png
+++ b/res/drawable-mdpi/ic_widget.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_widget_pressed.png b/res/drawable-mdpi/ic_widget_pressed.png
index 9b690d9..634b415 100644
--- a/res/drawable-mdpi/ic_widget_pressed.png
+++ b/res/drawable-mdpi/ic_widget_pressed.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_widget_resize_handle.png b/res/drawable-mdpi/ic_widget_resize_handle.png
index c3b287f..656e96c 100644
--- a/res/drawable-mdpi/ic_widget_resize_handle.png
+++ b/res/drawable-mdpi/ic_widget_resize_handle.png
Binary files differ
diff --git a/res/drawable-mdpi/page_hover_left.9.png b/res/drawable-mdpi/page_hover_left.9.png
deleted file mode 100644
index 2bbf428..0000000
--- a/res/drawable-mdpi/page_hover_left.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/page_hover_left_active.9.png b/res/drawable-mdpi/page_hover_left_active.9.png
deleted file mode 100644
index bf70f36..0000000
--- a/res/drawable-mdpi/page_hover_left_active.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/page_hover_right.9.png b/res/drawable-mdpi/page_hover_right.9.png
deleted file mode 100644
index 4bafd0f..0000000
--- a/res/drawable-mdpi/page_hover_right.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/page_hover_right_active.9.png b/res/drawable-mdpi/page_hover_right_active.9.png
deleted file mode 100644
index 4aaa014..0000000
--- a/res/drawable-mdpi/page_hover_right_active.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/portal_ring_inner.png b/res/drawable-mdpi/portal_ring_inner.png
index 99aaa60..7c5e2b7 100644
--- a/res/drawable-mdpi/portal_ring_inner.png
+++ b/res/drawable-mdpi/portal_ring_inner.png
Binary files differ
diff --git a/res/drawable-mdpi/portal_ring_inner_nolip.png b/res/drawable-mdpi/portal_ring_inner_nolip.png
index f981778..6ccdebb 100644
--- a/res/drawable-mdpi/portal_ring_inner_nolip.png
+++ b/res/drawable-mdpi/portal_ring_inner_nolip.png
Binary files differ
diff --git a/res/drawable-mdpi/portal_ring_outer.png b/res/drawable-mdpi/portal_ring_outer.png
index c98f64b..40a73ab 100644
--- a/res/drawable-mdpi/portal_ring_outer.png
+++ b/res/drawable-mdpi/portal_ring_outer.png
Binary files differ
diff --git a/res/drawable-mdpi/portal_ring_rest.png b/res/drawable-mdpi/portal_ring_rest.png
index 5c33b42..b2c733b 100644
--- a/res/drawable-mdpi/portal_ring_rest.png
+++ b/res/drawable-mdpi/portal_ring_rest.png
Binary files differ
diff --git a/res/drawable-mdpi/quantum_panel_bitmap.9.png b/res/drawable-mdpi/quantum_panel_bitmap.9.png
index 86be568..9325d49 100644
--- a/res/drawable-mdpi/quantum_panel_bitmap.9.png
+++ b/res/drawable-mdpi/quantum_panel_bitmap.9.png
Binary files differ
diff --git a/res/drawable-mdpi/quantum_panel_dark_bitmap.9.png b/res/drawable-mdpi/quantum_panel_dark_bitmap.9.png
index 70429b9..bf74fa0 100644
--- a/res/drawable-mdpi/quantum_panel_dark_bitmap.9.png
+++ b/res/drawable-mdpi/quantum_panel_dark_bitmap.9.png
Binary files differ
diff --git a/res/drawable-mdpi/screenpanel.9.png b/res/drawable-mdpi/screenpanel.9.png
index c2779fc..9603c12 100644
--- a/res/drawable-mdpi/screenpanel.9.png
+++ b/res/drawable-mdpi/screenpanel.9.png
Binary files differ
diff --git a/res/drawable-mdpi/screenpanel_hover.9.png b/res/drawable-mdpi/screenpanel_hover.9.png
index 70b3078..7f28ce0 100644
--- a/res/drawable-mdpi/screenpanel_hover.9.png
+++ b/res/drawable-mdpi/screenpanel_hover.9.png
Binary files differ
diff --git a/res/drawable-mdpi/virtual_preload.9.png b/res/drawable-mdpi/virtual_preload.9.png
index a3c7519..c4a01fe 100644
--- a/res/drawable-mdpi/virtual_preload.9.png
+++ b/res/drawable-mdpi/virtual_preload.9.png
Binary files differ
diff --git a/res/drawable-mdpi/virtual_preload_folder.9.png b/res/drawable-mdpi/virtual_preload_folder.9.png
index fa2f131..2f3e420 100644
--- a/res/drawable-mdpi/virtual_preload_folder.9.png
+++ b/res/drawable-mdpi/virtual_preload_folder.9.png
Binary files differ
diff --git a/res/drawable-mdpi/widget_resize_frame.9.png b/res/drawable-mdpi/widget_resize_frame.9.png
index 856cec6..252482f 100644
--- a/res/drawable-mdpi/widget_resize_frame.9.png
+++ b/res/drawable-mdpi/widget_resize_frame.9.png
Binary files differ
diff --git a/res/drawable-mdpi/widget_resize_shadow.9.png b/res/drawable-mdpi/widget_resize_shadow.9.png
index 02522f3..a2010e2 100644
--- a/res/drawable-mdpi/widget_resize_shadow.9.png
+++ b/res/drawable-mdpi/widget_resize_shadow.9.png
Binary files differ
diff --git a/res/drawable-mdpi/widget_tile.png b/res/drawable-mdpi/widget_tile.png
index 1ba559d..9652ace 100644
--- a/res/drawable-mdpi/widget_tile.png
+++ b/res/drawable-mdpi/widget_tile.png
Binary files differ
diff --git a/res/drawable-mdpi/workspace_bg.9.png b/res/drawable-mdpi/workspace_bg.9.png
index 2856e09..c67c432 100644
--- a/res/drawable-mdpi/workspace_bg.9.png
+++ b/res/drawable-mdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable-nodpi/ic_migration.png b/res/drawable-nodpi/ic_migration.png
index c282cd2..14f8721 100644
--- a/res/drawable-nodpi/ic_migration.png
+++ b/res/drawable-nodpi/ic_migration.png
Binary files differ
diff --git a/res/drawable-sw720dp-hdpi/workspace_bg.9.png b/res/drawable-sw720dp-hdpi/workspace_bg.9.png
index 5bbfa4f..ff75186 100644
--- a/res/drawable-sw720dp-hdpi/workspace_bg.9.png
+++ b/res/drawable-sw720dp-hdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable-sw720dp-mdpi/workspace_bg.9.png b/res/drawable-sw720dp-mdpi/workspace_bg.9.png
index 2856e09..c67c432 100644
--- a/res/drawable-sw720dp-mdpi/workspace_bg.9.png
+++ b/res/drawable-sw720dp-mdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable-sw720dp-xhdpi/workspace_bg.9.png b/res/drawable-sw720dp-xhdpi/workspace_bg.9.png
index 72269f2..0b80cbf 100644
--- a/res/drawable-sw720dp-xhdpi/workspace_bg.9.png
+++ b/res/drawable-sw720dp-xhdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable-sw720dp-xxhdpi/workspace_bg.9.png b/res/drawable-sw720dp-xxhdpi/workspace_bg.9.png
index efc9b04..0d180c2 100644
--- a/res/drawable-sw720dp-xxhdpi/workspace_bg.9.png
+++ b/res/drawable-sw720dp-xxhdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/cling_bg.9.png b/res/drawable-xhdpi/cling_bg.9.png
index 4db356f..7d8b1f0 100644
--- a/res/drawable-xhdpi/cling_bg.9.png
+++ b/res/drawable-xhdpi/cling_bg.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_all_apps_bg_hand.png b/res/drawable-xhdpi/ic_all_apps_bg_hand.png
new file mode 100644
index 0000000..8a67245
--- /dev/null
+++ b/res/drawable-xhdpi/ic_all_apps_bg_hand.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_all_apps_bg_icon_1.png b/res/drawable-xhdpi/ic_all_apps_bg_icon_1.png
new file mode 100644
index 0000000..c0ebaed
--- /dev/null
+++ b/res/drawable-xhdpi/ic_all_apps_bg_icon_1.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_all_apps_bg_icon_2.png b/res/drawable-xhdpi/ic_all_apps_bg_icon_2.png
new file mode 100644
index 0000000..71cf250
--- /dev/null
+++ b/res/drawable-xhdpi/ic_all_apps_bg_icon_2.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_all_apps_bg_icon_3.png b/res/drawable-xhdpi/ic_all_apps_bg_icon_3.png
new file mode 100644
index 0000000..3c69fc5
--- /dev/null
+++ b/res/drawable-xhdpi/ic_all_apps_bg_icon_3.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_all_apps_bg_icon_4.png b/res/drawable-xhdpi/ic_all_apps_bg_icon_4.png
new file mode 100644
index 0000000..5f6ca38
--- /dev/null
+++ b/res/drawable-xhdpi/ic_all_apps_bg_icon_4.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_allapps.png b/res/drawable-xhdpi/ic_allapps.png
index ff3d823..c11c103 100644
--- a/res/drawable-xhdpi/ic_allapps.png
+++ b/res/drawable-xhdpi/ic_allapps.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_allapps_pressed.png b/res/drawable-xhdpi/ic_allapps_pressed.png
index 5f188f6..f319bf1 100644
--- a/res/drawable-xhdpi/ic_allapps_pressed.png
+++ b/res/drawable-xhdpi/ic_allapps_pressed.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_arrow_back_grey.png b/res/drawable-xhdpi/ic_arrow_back_grey.png
index 11996ef..22854bf 100755
--- a/res/drawable-xhdpi/ic_arrow_back_grey.png
+++ b/res/drawable-xhdpi/ic_arrow_back_grey.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_info_launcher.png b/res/drawable-xhdpi/ic_info_launcher.png
index 076b59b..041f2b3 100644
--- a/res/drawable-xhdpi/ic_info_launcher.png
+++ b/res/drawable-xhdpi/ic_info_launcher.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pageindicator_add.png b/res/drawable-xhdpi/ic_pageindicator_add.png
index af1da2d..7e18c05 100644
--- a/res/drawable-xhdpi/ic_pageindicator_add.png
+++ b/res/drawable-xhdpi/ic_pageindicator_add.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pageindicator_current.png b/res/drawable-xhdpi/ic_pageindicator_current.png
index 3054f2f..866725f 100644
--- a/res/drawable-xhdpi/ic_pageindicator_current.png
+++ b/res/drawable-xhdpi/ic_pageindicator_current.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pageindicator_current_folder.png b/res/drawable-xhdpi/ic_pageindicator_current_folder.png
index cd92e9f..ec19b7c 100644
--- a/res/drawable-xhdpi/ic_pageindicator_current_folder.png
+++ b/res/drawable-xhdpi/ic_pageindicator_current_folder.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pageindicator_default.png b/res/drawable-xhdpi/ic_pageindicator_default.png
index 38538dc..0cde8f4 100644
--- a/res/drawable-xhdpi/ic_pageindicator_default.png
+++ b/res/drawable-xhdpi/ic_pageindicator_default.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pageindicator_default_folder.png b/res/drawable-xhdpi/ic_pageindicator_default_folder.png
index e7c46e3..7c22d41 100644
--- a/res/drawable-xhdpi/ic_pageindicator_default_folder.png
+++ b/res/drawable-xhdpi/ic_pageindicator_default_folder.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_remove_launcher.png b/res/drawable-xhdpi/ic_remove_launcher.png
index de05889..ff94eb8 100644
--- a/res/drawable-xhdpi/ic_remove_launcher.png
+++ b/res/drawable-xhdpi/ic_remove_launcher.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_search_grey.png b/res/drawable-xhdpi/ic_search_grey.png
index e83891c..e63182d 100755
--- a/res/drawable-xhdpi/ic_search_grey.png
+++ b/res/drawable-xhdpi/ic_search_grey.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_setting.png b/res/drawable-xhdpi/ic_setting.png
index 3a7310b..bb90789 100644
--- a/res/drawable-xhdpi/ic_setting.png
+++ b/res/drawable-xhdpi/ic_setting.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_setting_pressed.png b/res/drawable-xhdpi/ic_setting_pressed.png
index 005d49c..949373f 100644
--- a/res/drawable-xhdpi/ic_setting_pressed.png
+++ b/res/drawable-xhdpi/ic_setting_pressed.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_uninstall_launcher.png b/res/drawable-xhdpi/ic_uninstall_launcher.png
index dd50e88..2c7ab56 100644
--- a/res/drawable-xhdpi/ic_uninstall_launcher.png
+++ b/res/drawable-xhdpi/ic_uninstall_launcher.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_wallpaper.png b/res/drawable-xhdpi/ic_wallpaper.png
index d2bf246..0acf773 100644
--- a/res/drawable-xhdpi/ic_wallpaper.png
+++ b/res/drawable-xhdpi/ic_wallpaper.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_wallpaper_pressed.png b/res/drawable-xhdpi/ic_wallpaper_pressed.png
index 5a9b84d..e1e291d 100644
--- a/res/drawable-xhdpi/ic_wallpaper_pressed.png
+++ b/res/drawable-xhdpi/ic_wallpaper_pressed.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_widget.png b/res/drawable-xhdpi/ic_widget.png
index cf6be81..94bb79f 100644
--- a/res/drawable-xhdpi/ic_widget.png
+++ b/res/drawable-xhdpi/ic_widget.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_widget_pressed.png b/res/drawable-xhdpi/ic_widget_pressed.png
index 633c9c6..1dcaf37 100644
--- a/res/drawable-xhdpi/ic_widget_pressed.png
+++ b/res/drawable-xhdpi/ic_widget_pressed.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_widget_resize_handle.png b/res/drawable-xhdpi/ic_widget_resize_handle.png
index f445a1c..0e8dd68 100644
--- a/res/drawable-xhdpi/ic_widget_resize_handle.png
+++ b/res/drawable-xhdpi/ic_widget_resize_handle.png
Binary files differ
diff --git a/res/drawable-xhdpi/page_hover_left.9.png b/res/drawable-xhdpi/page_hover_left.9.png
deleted file mode 100644
index a2b9b65..0000000
--- a/res/drawable-xhdpi/page_hover_left.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/page_hover_left_active.9.png b/res/drawable-xhdpi/page_hover_left_active.9.png
deleted file mode 100644
index ba9478e..0000000
--- a/res/drawable-xhdpi/page_hover_left_active.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/page_hover_right.9.png b/res/drawable-xhdpi/page_hover_right.9.png
deleted file mode 100644
index 1243ea9..0000000
--- a/res/drawable-xhdpi/page_hover_right.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/page_hover_right_active.9.png b/res/drawable-xhdpi/page_hover_right_active.9.png
deleted file mode 100644
index 582261c..0000000
--- a/res/drawable-xhdpi/page_hover_right_active.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/portal_ring_inner.png b/res/drawable-xhdpi/portal_ring_inner.png
index 7b6a8a0..b088042 100644
--- a/res/drawable-xhdpi/portal_ring_inner.png
+++ b/res/drawable-xhdpi/portal_ring_inner.png
Binary files differ
diff --git a/res/drawable-xhdpi/portal_ring_inner_nolip.png b/res/drawable-xhdpi/portal_ring_inner_nolip.png
index 4b84436..decf766 100644
--- a/res/drawable-xhdpi/portal_ring_inner_nolip.png
+++ b/res/drawable-xhdpi/portal_ring_inner_nolip.png
Binary files differ
diff --git a/res/drawable-xhdpi/portal_ring_outer.png b/res/drawable-xhdpi/portal_ring_outer.png
index 79c1888..5ab9a21 100644
--- a/res/drawable-xhdpi/portal_ring_outer.png
+++ b/res/drawable-xhdpi/portal_ring_outer.png
Binary files differ
diff --git a/res/drawable-xhdpi/portal_ring_rest.png b/res/drawable-xhdpi/portal_ring_rest.png
index 544a74f..7d1c842 100644
--- a/res/drawable-xhdpi/portal_ring_rest.png
+++ b/res/drawable-xhdpi/portal_ring_rest.png
Binary files differ
diff --git a/res/drawable-xhdpi/quantum_panel_bitmap.9.png b/res/drawable-xhdpi/quantum_panel_bitmap.9.png
index 13bdf09..b89e8b4 100644
--- a/res/drawable-xhdpi/quantum_panel_bitmap.9.png
+++ b/res/drawable-xhdpi/quantum_panel_bitmap.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/quantum_panel_dark_bitmap.9.png b/res/drawable-xhdpi/quantum_panel_dark_bitmap.9.png
index ac2e423..1d17136 100644
--- a/res/drawable-xhdpi/quantum_panel_dark_bitmap.9.png
+++ b/res/drawable-xhdpi/quantum_panel_dark_bitmap.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/screenpanel.9.png b/res/drawable-xhdpi/screenpanel.9.png
index 53a7812..75343f7 100644
--- a/res/drawable-xhdpi/screenpanel.9.png
+++ b/res/drawable-xhdpi/screenpanel.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/screenpanel_hover.9.png b/res/drawable-xhdpi/screenpanel_hover.9.png
index a2e200f..55b4d6e 100644
--- a/res/drawable-xhdpi/screenpanel_hover.9.png
+++ b/res/drawable-xhdpi/screenpanel_hover.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/virtual_preload.9.png b/res/drawable-xhdpi/virtual_preload.9.png
index d2c3fea..2afade1 100644
--- a/res/drawable-xhdpi/virtual_preload.9.png
+++ b/res/drawable-xhdpi/virtual_preload.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/virtual_preload_folder.9.png b/res/drawable-xhdpi/virtual_preload_folder.9.png
index 1f9202b..cb3fdca 100644
--- a/res/drawable-xhdpi/virtual_preload_folder.9.png
+++ b/res/drawable-xhdpi/virtual_preload_folder.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/widget_resize_frame.9.png b/res/drawable-xhdpi/widget_resize_frame.9.png
index bf7cc6b..563c75d 100644
--- a/res/drawable-xhdpi/widget_resize_frame.9.png
+++ b/res/drawable-xhdpi/widget_resize_frame.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/widget_resize_shadow.9.png b/res/drawable-xhdpi/widget_resize_shadow.9.png
index 96dee7f..2b1ac05 100644
--- a/res/drawable-xhdpi/widget_resize_shadow.9.png
+++ b/res/drawable-xhdpi/widget_resize_shadow.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/widget_tile.png b/res/drawable-xhdpi/widget_tile.png
index 9730f35..be1748d 100644
--- a/res/drawable-xhdpi/widget_tile.png
+++ b/res/drawable-xhdpi/widget_tile.png
Binary files differ
diff --git a/res/drawable-xhdpi/workspace_bg.9.png b/res/drawable-xhdpi/workspace_bg.9.png
index 72269f2..0b80cbf 100644
--- a/res/drawable-xhdpi/workspace_bg.9.png
+++ b/res/drawable-xhdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/cling_bg.9.png b/res/drawable-xxhdpi/cling_bg.9.png
index dc9f69a..d31ea70 100644
--- a/res/drawable-xxhdpi/cling_bg.9.png
+++ b/res/drawable-xxhdpi/cling_bg.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_all_apps_bg_hand.png b/res/drawable-xxhdpi/ic_all_apps_bg_hand.png
new file mode 100644
index 0000000..ed694f8
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_all_apps_bg_hand.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_all_apps_bg_icon_1.png b/res/drawable-xxhdpi/ic_all_apps_bg_icon_1.png
new file mode 100644
index 0000000..5cb0427
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_all_apps_bg_icon_1.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_all_apps_bg_icon_2.png b/res/drawable-xxhdpi/ic_all_apps_bg_icon_2.png
new file mode 100644
index 0000000..cd0322b
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_all_apps_bg_icon_2.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_all_apps_bg_icon_3.png b/res/drawable-xxhdpi/ic_all_apps_bg_icon_3.png
new file mode 100644
index 0000000..19ffc2d
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_all_apps_bg_icon_3.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_all_apps_bg_icon_4.png b/res/drawable-xxhdpi/ic_all_apps_bg_icon_4.png
new file mode 100644
index 0000000..311c3df
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_all_apps_bg_icon_4.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_allapps.png b/res/drawable-xxhdpi/ic_allapps.png
index 5dbfe4c..cf6a2cb 100644
--- a/res/drawable-xxhdpi/ic_allapps.png
+++ b/res/drawable-xxhdpi/ic_allapps.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_allapps_pressed.png b/res/drawable-xxhdpi/ic_allapps_pressed.png
index e761723..379389a 100644
--- a/res/drawable-xxhdpi/ic_allapps_pressed.png
+++ b/res/drawable-xxhdpi/ic_allapps_pressed.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_arrow_back_grey.png b/res/drawable-xxhdpi/ic_arrow_back_grey.png
index ccd3900..a3ed052 100755
--- a/res/drawable-xxhdpi/ic_arrow_back_grey.png
+++ b/res/drawable-xxhdpi/ic_arrow_back_grey.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_info_launcher.png b/res/drawable-xxhdpi/ic_info_launcher.png
index 386d091..8e602da 100644
--- a/res/drawable-xxhdpi/ic_info_launcher.png
+++ b/res/drawable-xxhdpi/ic_info_launcher.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_pageindicator_add.png b/res/drawable-xxhdpi/ic_pageindicator_add.png
index c288952..d790e86 100644
--- a/res/drawable-xxhdpi/ic_pageindicator_add.png
+++ b/res/drawable-xxhdpi/ic_pageindicator_add.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_pageindicator_current.png b/res/drawable-xxhdpi/ic_pageindicator_current.png
index 5941c8e..9550c61 100644
--- a/res/drawable-xxhdpi/ic_pageindicator_current.png
+++ b/res/drawable-xxhdpi/ic_pageindicator_current.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_pageindicator_current_folder.png b/res/drawable-xxhdpi/ic_pageindicator_current_folder.png
index 602b89a..987c882 100644
--- a/res/drawable-xxhdpi/ic_pageindicator_current_folder.png
+++ b/res/drawable-xxhdpi/ic_pageindicator_current_folder.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_pageindicator_default.png b/res/drawable-xxhdpi/ic_pageindicator_default.png
index 3fa9e5f..3bee96f 100644
--- a/res/drawable-xxhdpi/ic_pageindicator_default.png
+++ b/res/drawable-xxhdpi/ic_pageindicator_default.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_pageindicator_default_folder.png b/res/drawable-xxhdpi/ic_pageindicator_default_folder.png
index bbcd7f9..46ff473 100644
--- a/res/drawable-xxhdpi/ic_pageindicator_default_folder.png
+++ b/res/drawable-xxhdpi/ic_pageindicator_default_folder.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_remove_launcher.png b/res/drawable-xxhdpi/ic_remove_launcher.png
index 7c28bb0..78ca080 100644
--- a/res/drawable-xxhdpi/ic_remove_launcher.png
+++ b/res/drawable-xxhdpi/ic_remove_launcher.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_search_grey.png b/res/drawable-xxhdpi/ic_search_grey.png
index f4c5e27..33b4ea9 100755
--- a/res/drawable-xxhdpi/ic_search_grey.png
+++ b/res/drawable-xxhdpi/ic_search_grey.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_setting.png b/res/drawable-xxhdpi/ic_setting.png
index 01bdcd5..3effb50 100644
--- a/res/drawable-xxhdpi/ic_setting.png
+++ b/res/drawable-xxhdpi/ic_setting.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_setting_pressed.png b/res/drawable-xxhdpi/ic_setting_pressed.png
index d0cad5e..d78cad6 100644
--- a/res/drawable-xxhdpi/ic_setting_pressed.png
+++ b/res/drawable-xxhdpi/ic_setting_pressed.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_uninstall_launcher.png b/res/drawable-xxhdpi/ic_uninstall_launcher.png
index 872e829..43aba6e 100644
--- a/res/drawable-xxhdpi/ic_uninstall_launcher.png
+++ b/res/drawable-xxhdpi/ic_uninstall_launcher.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_wallpaper.png b/res/drawable-xxhdpi/ic_wallpaper.png
index 490c45a..218fd1d 100644
--- a/res/drawable-xxhdpi/ic_wallpaper.png
+++ b/res/drawable-xxhdpi/ic_wallpaper.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_wallpaper_pressed.png b/res/drawable-xxhdpi/ic_wallpaper_pressed.png
index e5d200b..52c92cb 100644
--- a/res/drawable-xxhdpi/ic_wallpaper_pressed.png
+++ b/res/drawable-xxhdpi/ic_wallpaper_pressed.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_widget.png b/res/drawable-xxhdpi/ic_widget.png
index d4b8324..cc5002e 100644
--- a/res/drawable-xxhdpi/ic_widget.png
+++ b/res/drawable-xxhdpi/ic_widget.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_widget_pressed.png b/res/drawable-xxhdpi/ic_widget_pressed.png
index b8dd35d..0c9b02a 100644
--- a/res/drawable-xxhdpi/ic_widget_pressed.png
+++ b/res/drawable-xxhdpi/ic_widget_pressed.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_widget_resize_handle.png b/res/drawable-xxhdpi/ic_widget_resize_handle.png
index 144cac9..736a157 100644
--- a/res/drawable-xxhdpi/ic_widget_resize_handle.png
+++ b/res/drawable-xxhdpi/ic_widget_resize_handle.png
Binary files differ
diff --git a/res/drawable-xxhdpi/page_hover_left.9.png b/res/drawable-xxhdpi/page_hover_left.9.png
deleted file mode 100644
index 63869dd..0000000
--- a/res/drawable-xxhdpi/page_hover_left.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/page_hover_left_active.9.png b/res/drawable-xxhdpi/page_hover_left_active.9.png
deleted file mode 100644
index 9a418ce..0000000
--- a/res/drawable-xxhdpi/page_hover_left_active.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/page_hover_right.9.png b/res/drawable-xxhdpi/page_hover_right.9.png
deleted file mode 100644
index c6fd398..0000000
--- a/res/drawable-xxhdpi/page_hover_right.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/page_hover_right_active.9.png b/res/drawable-xxhdpi/page_hover_right_active.9.png
deleted file mode 100644
index 7aef373..0000000
--- a/res/drawable-xxhdpi/page_hover_right_active.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/portal_ring_inner.png b/res/drawable-xxhdpi/portal_ring_inner.png
index d088a17..cd23cf7 100644
--- a/res/drawable-xxhdpi/portal_ring_inner.png
+++ b/res/drawable-xxhdpi/portal_ring_inner.png
Binary files differ
diff --git a/res/drawable-xxhdpi/portal_ring_inner_nolip.png b/res/drawable-xxhdpi/portal_ring_inner_nolip.png
index 0fad656..d82b910 100644
--- a/res/drawable-xxhdpi/portal_ring_inner_nolip.png
+++ b/res/drawable-xxhdpi/portal_ring_inner_nolip.png
Binary files differ
diff --git a/res/drawable-xxhdpi/portal_ring_outer.png b/res/drawable-xxhdpi/portal_ring_outer.png
index 45ac040..e5d33b2 100644
--- a/res/drawable-xxhdpi/portal_ring_outer.png
+++ b/res/drawable-xxhdpi/portal_ring_outer.png
Binary files differ
diff --git a/res/drawable-xxhdpi/portal_ring_rest.png b/res/drawable-xxhdpi/portal_ring_rest.png
index 6fa6a53..d52825c 100644
--- a/res/drawable-xxhdpi/portal_ring_rest.png
+++ b/res/drawable-xxhdpi/portal_ring_rest.png
Binary files differ
diff --git a/res/drawable-xxhdpi/quantum_panel_bitmap.9.png b/res/drawable-xxhdpi/quantum_panel_bitmap.9.png
index b44269e..1dd1f6d 100644
--- a/res/drawable-xxhdpi/quantum_panel_bitmap.9.png
+++ b/res/drawable-xxhdpi/quantum_panel_bitmap.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/quantum_panel_dark_bitmap.9.png b/res/drawable-xxhdpi/quantum_panel_dark_bitmap.9.png
index 7979cf7..48d584b 100644
--- a/res/drawable-xxhdpi/quantum_panel_dark_bitmap.9.png
+++ b/res/drawable-xxhdpi/quantum_panel_dark_bitmap.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/screenpanel.9.png b/res/drawable-xxhdpi/screenpanel.9.png
index 2d13954..b221b37 100644
--- a/res/drawable-xxhdpi/screenpanel.9.png
+++ b/res/drawable-xxhdpi/screenpanel.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/screenpanel_hover.9.png b/res/drawable-xxhdpi/screenpanel_hover.9.png
index 369fc44..418cf0a 100644
--- a/res/drawable-xxhdpi/screenpanel_hover.9.png
+++ b/res/drawable-xxhdpi/screenpanel_hover.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/virtual_preload.9.png b/res/drawable-xxhdpi/virtual_preload.9.png
index 93e3b33..03c6e7f 100644
--- a/res/drawable-xxhdpi/virtual_preload.9.png
+++ b/res/drawable-xxhdpi/virtual_preload.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/virtual_preload_folder.9.png b/res/drawable-xxhdpi/virtual_preload_folder.9.png
index fae19b3..052a72e 100644
--- a/res/drawable-xxhdpi/virtual_preload_folder.9.png
+++ b/res/drawable-xxhdpi/virtual_preload_folder.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/widget_resize_frame.9.png b/res/drawable-xxhdpi/widget_resize_frame.9.png
index 7e189d4..ea527f4 100644
--- a/res/drawable-xxhdpi/widget_resize_frame.9.png
+++ b/res/drawable-xxhdpi/widget_resize_frame.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/widget_resize_shadow.9.png b/res/drawable-xxhdpi/widget_resize_shadow.9.png
index 41c448b..5412168 100644
--- a/res/drawable-xxhdpi/widget_resize_shadow.9.png
+++ b/res/drawable-xxhdpi/widget_resize_shadow.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/widget_tile.png b/res/drawable-xxhdpi/widget_tile.png
index 3a3790d..c6237db 100644
--- a/res/drawable-xxhdpi/widget_tile.png
+++ b/res/drawable-xxhdpi/widget_tile.png
Binary files differ
diff --git a/res/drawable-xxhdpi/workspace_bg.9.png b/res/drawable-xxhdpi/workspace_bg.9.png
index efc9b04..0d180c2 100644
--- a/res/drawable-xxhdpi/workspace_bg.9.png
+++ b/res/drawable-xxhdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_all_apps_bg_hand.png b/res/drawable-xxxhdpi/ic_all_apps_bg_hand.png
new file mode 100644
index 0000000..615374a
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_all_apps_bg_hand.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_all_apps_bg_icon_1.png b/res/drawable-xxxhdpi/ic_all_apps_bg_icon_1.png
new file mode 100644
index 0000000..10f8c41
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_all_apps_bg_icon_1.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_all_apps_bg_icon_2.png b/res/drawable-xxxhdpi/ic_all_apps_bg_icon_2.png
new file mode 100644
index 0000000..102d925
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_all_apps_bg_icon_2.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_all_apps_bg_icon_3.png b/res/drawable-xxxhdpi/ic_all_apps_bg_icon_3.png
new file mode 100644
index 0000000..9be5b7a
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_all_apps_bg_icon_3.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_all_apps_bg_icon_4.png b/res/drawable-xxxhdpi/ic_all_apps_bg_icon_4.png
new file mode 100644
index 0000000..d7fb29b
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_all_apps_bg_icon_4.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_arrow_back_grey.png b/res/drawable-xxxhdpi/ic_arrow_back_grey.png
index 79b9b48..6b42051 100755
--- a/res/drawable-xxxhdpi/ic_arrow_back_grey.png
+++ b/res/drawable-xxxhdpi/ic_arrow_back_grey.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_info_launcher.png b/res/drawable-xxxhdpi/ic_info_launcher.png
index bf39e5b..3540de1 100644
--- a/res/drawable-xxxhdpi/ic_info_launcher.png
+++ b/res/drawable-xxxhdpi/ic_info_launcher.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_remove_launcher.png b/res/drawable-xxxhdpi/ic_remove_launcher.png
index 7043be0..418d81a 100644
--- a/res/drawable-xxxhdpi/ic_remove_launcher.png
+++ b/res/drawable-xxxhdpi/ic_remove_launcher.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_search_grey.png b/res/drawable-xxxhdpi/ic_search_grey.png
index bd5fdf4..d957186 100755
--- a/res/drawable-xxxhdpi/ic_search_grey.png
+++ b/res/drawable-xxxhdpi/ic_search_grey.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_uninstall_launcher.png b/res/drawable-xxxhdpi/ic_uninstall_launcher.png
index 77a3302..724437a 100644
--- a/res/drawable-xxxhdpi/ic_uninstall_launcher.png
+++ b/res/drawable-xxxhdpi/ic_uninstall_launcher.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_widget_resize_handle.png b/res/drawable-xxxhdpi/ic_widget_resize_handle.png
index 4bde6b9..e3c45ad 100644
--- a/res/drawable-xxxhdpi/ic_widget_resize_handle.png
+++ b/res/drawable-xxxhdpi/ic_widget_resize_handle.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/portal_ring_inner.png b/res/drawable-xxxhdpi/portal_ring_inner.png
index 34a3599..59e811d 100644
--- a/res/drawable-xxxhdpi/portal_ring_inner.png
+++ b/res/drawable-xxxhdpi/portal_ring_inner.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/portal_ring_inner_nolip.png b/res/drawable-xxxhdpi/portal_ring_inner_nolip.png
index 8cebb35..c1e7585 100644
--- a/res/drawable-xxxhdpi/portal_ring_inner_nolip.png
+++ b/res/drawable-xxxhdpi/portal_ring_inner_nolip.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/portal_ring_outer.png b/res/drawable-xxxhdpi/portal_ring_outer.png
index d2df322..f2f818b 100644
--- a/res/drawable-xxxhdpi/portal_ring_outer.png
+++ b/res/drawable-xxxhdpi/portal_ring_outer.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/portal_ring_rest.png b/res/drawable-xxxhdpi/portal_ring_rest.png
index 11e92ee..2af67b8 100644
--- a/res/drawable-xxxhdpi/portal_ring_rest.png
+++ b/res/drawable-xxxhdpi/portal_ring_rest.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/quantum_panel_bitmap.9.png b/res/drawable-xxxhdpi/quantum_panel_bitmap.9.png
index bc887fe..915177d 100644
--- a/res/drawable-xxxhdpi/quantum_panel_bitmap.9.png
+++ b/res/drawable-xxxhdpi/quantum_panel_bitmap.9.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/quantum_panel_dark_bitmap.9.png b/res/drawable-xxxhdpi/quantum_panel_dark_bitmap.9.png
index 7cfd6e4..27b8466 100644
--- a/res/drawable-xxxhdpi/quantum_panel_dark_bitmap.9.png
+++ b/res/drawable-xxxhdpi/quantum_panel_dark_bitmap.9.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/widget_resize_frame.9.png b/res/drawable-xxxhdpi/widget_resize_frame.9.png
index cb609ce..4644e9a 100644
--- a/res/drawable-xxxhdpi/widget_resize_frame.9.png
+++ b/res/drawable-xxxhdpi/widget_resize_frame.9.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/widget_resize_shadow.9.png b/res/drawable-xxxhdpi/widget_resize_shadow.9.png
index 82c8b9c..63cea84 100644
--- a/res/drawable-xxxhdpi/widget_resize_shadow.9.png
+++ b/res/drawable-xxxhdpi/widget_resize_shadow.9.png
Binary files differ
diff --git a/res/drawable/ic_setting_pressed.xml b/res/drawable/ic_setting_pressed.xml
new file mode 100644
index 0000000..689f833
--- /dev/null
+++ b/res/drawable/ic_setting_pressed.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2015, 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.
+-->
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+    android:alpha="0.5"
+    android:src="@drawable/ic_setting" />
diff --git a/res/drawable/ic_wallpaper_pressed.xml b/res/drawable/ic_wallpaper_pressed.xml
new file mode 100644
index 0000000..d241c7d
--- /dev/null
+++ b/res/drawable/ic_wallpaper_pressed.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2015, 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.
+-->
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+    android:alpha="0.5"
+    android:src="@drawable/ic_wallpaper" />
diff --git a/res/drawable/ic_widget_pressed.xml b/res/drawable/ic_widget_pressed.xml
new file mode 100644
index 0000000..44ac5b6
--- /dev/null
+++ b/res/drawable/ic_widget_pressed.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2015, 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.
+-->
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+    android:alpha="0.5"
+    android:src="@drawable/ic_widget" />
diff --git a/res/layout-land/launcher.xml b/res/layout-land/launcher.xml
index 6500ebc..951a30e 100644
--- a/res/layout-land/launcher.xml
+++ b/res/layout-land/launcher.xml
@@ -23,7 +23,7 @@
     android:layout_height="match_parent"
     android:fitsSystemWindows="true">
 
-    <com.android.launcher3.DragLayer
+    <com.android.launcher3.dragndrop.DragLayer
         android:id="@+id/drag_layer"
         android:layout_width="match_parent"
         android:layout_height="match_parent">
@@ -50,6 +50,10 @@
             android:layout_gravity="right" />
 
         <include
+            android:id="@+id/app_info_drop_target_bar"
+            layout="@layout/app_info_drop_target_bar" />
+
+        <include
             android:id="@+id/search_drop_target_bar"
             layout="@layout/search_drop_target_bar" />
 
@@ -68,7 +72,7 @@
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:visibility="invisible" />
-    </com.android.launcher3.DragLayer>
+    </com.android.launcher3.dragndrop.DragLayer>
 
     <ViewStub
         android:id="@+id/launcher_overlay_stub"
diff --git a/res/layout-port/launcher.xml b/res/layout-port/launcher.xml
index d0772ee..4cb34e9 100644
--- a/res/layout-port/launcher.xml
+++ b/res/layout-port/launcher.xml
@@ -24,7 +24,7 @@
     android:layout_height="match_parent"
     android:fitsSystemWindows="true">
 
-    <com.android.launcher3.DragLayer
+    <com.android.launcher3.dragndrop.DragLayer
         android:id="@+id/drag_layer"
         android:layout_width="match_parent"
         android:layout_height="match_parent">
@@ -64,6 +64,10 @@
             android:layout_gravity="center_horizontal" />
 
         <include
+            android:id="@+id/app_info_drop_target_bar"
+            layout="@layout/app_info_drop_target_bar" />
+
+        <include
             android:id="@+id/search_drop_target_bar"
             layout="@layout/search_drop_target_bar" />
 
@@ -78,7 +82,7 @@
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:visibility="invisible" />
-    </com.android.launcher3.DragLayer>
+    </com.android.launcher3.dragndrop.DragLayer>
 
     <ViewStub
         android:id="@+id/launcher_overlay_stub"
diff --git a/res/layout-sw720dp/launcher.xml b/res/layout-sw720dp/launcher.xml
index 802922e..3228999 100644
--- a/res/layout-sw720dp/launcher.xml
+++ b/res/layout-sw720dp/launcher.xml
@@ -23,7 +23,7 @@
     android:layout_height="match_parent"
     android:fitsSystemWindows="true">
 
-    <com.android.launcher3.DragLayer
+    <com.android.launcher3.dragndrop.DragLayer
         android:id="@+id/drag_layer"
         android:layout_width="match_parent"
         android:layout_height="match_parent">
@@ -50,6 +50,10 @@
             android:layout_height="match_parent" />
 
         <include
+            android:id="@+id/app_info_drop_target_bar"
+            layout="@layout/app_info_drop_target_bar" />
+
+        <include
             android:id="@+id/search_drop_target_bar"
             layout="@layout/search_drop_target_bar" />
 
@@ -76,7 +80,7 @@
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:visibility="invisible" />
-    </com.android.launcher3.DragLayer>
+    </com.android.launcher3.dragndrop.DragLayer>
 
     <ViewStub
         android:id="@+id/launcher_overlay_stub"
diff --git a/res/layout-v21/overview_panel.xml b/res/layout-v21/overview_panel.xml
new file mode 100644
index 0000000..fb6b512
--- /dev/null
+++ b/res/layout-v21/overview_panel.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center_horizontal|bottom"
+    android:gravity="top"
+    android:orientation="horizontal" >
+
+    <TextView
+        android:id="@+id/wallpaper_button"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:drawablePadding="4dp"
+        android:drawableTop="@drawable/ic_wallpaper"
+        android:fontFamily="sans-serif-condensed"
+        android:gravity="center_horizontal"
+        android:stateListAnimator="@animator/overview_button_anim"
+        android:text="@string/wallpaper_button_text"
+        android:textAllCaps="true"
+        android:textColor="@android:color/white"
+        android:textSize="12sp" />
+
+    <TextView
+        android:id="@+id/widget_button"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:drawablePadding="4dp"
+        android:drawableTop="@drawable/ic_widget"
+        android:fontFamily="sans-serif-condensed"
+        android:gravity="center_horizontal"
+        android:stateListAnimator="@animator/overview_button_anim"
+        android:text="@string/widget_button_text"
+        android:textAllCaps="true"
+        android:textColor="@android:color/white"
+        android:textSize="12sp" />
+
+    <TextView
+        android:id="@+id/settings_button"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:drawablePadding="4dp"
+        android:drawableTop="@drawable/ic_setting"
+        android:fontFamily="sans-serif-condensed"
+        android:gravity="center_horizontal"
+        android:stateListAnimator="@animator/overview_button_anim"
+        android:text="@string/settings_button_text"
+        android:textAllCaps="true"
+        android:textColor="@android:color/white"
+        android:textSize="12sp" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/all_apps_empty_search.xml b/res/layout/all_apps_empty_search.xml
index b9b493e..5439111 100644
--- a/res/layout/all_apps_empty_search.xml
+++ b/res/layout/all_apps_empty_search.xml
@@ -19,7 +19,7 @@
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:gravity="start"
-    android:paddingTop="20dp"
+    android:paddingTop="@dimen/all_apps_empty_search_message_top_offset"
     android:paddingBottom="8dp"
     android:paddingLeft="16dp"
     android:paddingRight="16dp"
diff --git a/res/layout/all_apps_search_market.xml b/res/layout/all_apps_search_market.xml
index 1282069..a2bb2e7 100644
--- a/res/layout/all_apps_search_market.xml
+++ b/res/layout/all_apps_search_market.xml
@@ -23,7 +23,7 @@
     android:paddingRight="16dp"
     android:fontFamily="sans-serif-medium"
     android:textSize="14sp"
-    android:textColor="#009688"
+    android:textColor="@color/launcher_accent_color"
     android:textAllCaps="true"
-    android:focusable="false"
+    android:focusable="true"
     android:background="@drawable/all_apps_search_market_bg" />
diff --git a/res/layout/app_info_drop_target_bar.xml b/res/layout/app_info_drop_target_bar.xml
new file mode 100644
index 0000000..b8f30d0
--- /dev/null
+++ b/res/layout/app_info_drop_target_bar.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<com.android.launcher3.AppInfoDropTargetBar xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:focusable="false" >
+
+    <!-- Drag specific targets container -->
+    <LinearLayout
+        android:id="@+id/drag_target_bar"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_gravity="center|bottom" >
+
+        <FrameLayout
+            style="@style/DropTargetButtonContainer"
+            android:layout_weight="1" >
+
+            <!-- Info target -->
+
+            <com.android.launcher3.InfoDropTarget
+                android:id="@+id/info_target_text"
+                style="@style/DropTargetButton"
+                android:text="@string/info_target_label" />
+        </FrameLayout>
+    </LinearLayout>
+
+</com.android.launcher3.AppInfoDropTargetBar>
\ No newline at end of file
diff --git a/res/layout/overview_panel.xml b/res/layout/overview_panel.xml
index 4b7423e..1f02dce 100644
--- a/res/layout/overview_panel.xml
+++ b/res/layout/overview_panel.xml
@@ -32,6 +32,7 @@
         android:gravity="center_horizontal"
         android:text="@string/wallpaper_button_text"
         android:textAllCaps="true"
+        android:textColor="@android:color/white"
         android:textSize="12sp" />
 
     <TextView
@@ -45,6 +46,7 @@
         android:gravity="center_horizontal"
         android:text="@string/widget_button_text"
         android:textAllCaps="true"
+        android:textColor="@android:color/white"
         android:textSize="12sp" />
 
     <TextView
@@ -58,6 +60,7 @@
         android:gravity="center_horizontal"
         android:text="@string/settings_button_text"
         android:textAllCaps="true"
+        android:textColor="@android:color/white"
         android:textSize="12sp" />
 
 </LinearLayout>
\ No newline at end of file
diff --git a/res/layout/search_drop_target_bar.xml b/res/layout/search_drop_target_bar.xml
index 4737ee1..724eb94 100644
--- a/res/layout/search_drop_target_bar.xml
+++ b/res/layout/search_drop_target_bar.xml
@@ -43,18 +43,6 @@
             style="@style/DropTargetButtonContainer"
             android:layout_weight="1" >
 
-            <!-- Info target -->
-
-            <com.android.launcher3.InfoDropTarget
-                android:id="@+id/info_target_text"
-                style="@style/DropTargetButton"
-                android:text="@string/info_target_label" />
-        </FrameLayout>
-
-        <FrameLayout
-            style="@style/DropTargetButtonContainer"
-            android:layout_weight="1" >
-
             <!-- Uninstall target -->
 
             <com.android.launcher3.UninstallDropTarget
diff --git a/res/layout/user_folder.xml b/res/layout/user_folder.xml
index 1be9561..252ebf0 100644
--- a/res/layout/user_folder.xml
+++ b/res/layout/user_folder.xml
@@ -20,8 +20,7 @@
     android:layout_height="wrap_content"
     android:background="@drawable/quantum_panel"
     android:elevation="5dp"
-    android:orientation="vertical"
-    android:descendantFocusability="afterDescendants" >
+    android:orientation="vertical" >
 
     <FrameLayout
         android:id="@+id/folder_content_wrapper"
@@ -71,7 +70,6 @@
             android:textColor="#ff777777"
             android:textColorHighlight="#ffCCCCCC"
             android:textColorHint="#ff808080"
-            android:textCursorDrawable="@null"
             android:textSize="14sp" />
 
         <include
diff --git a/res/layout/widgets_list_row_view.xml b/res/layout/widgets_list_row_view.xml
index ced5648..1d593df 100644
--- a/res/layout/widgets_list_row_view.xml
+++ b/res/layout/widgets_list_row_view.xml
@@ -51,6 +51,7 @@
 
     <HorizontalScrollView
         android:id="@+id/widgets_scroll_container"
+        android:theme="@style/Theme.Dark.CustomOverscroll"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:scrollbars="none">
diff --git a/res/mipmap-hdpi/ic_launcher_home.png b/res/mipmap-hdpi/ic_launcher_home.png
index b556d7a..d068d92 100644
--- a/res/mipmap-hdpi/ic_launcher_home.png
+++ b/res/mipmap-hdpi/ic_launcher_home.png
Binary files differ
diff --git a/res/mipmap-mdpi/ic_launcher_home.png b/res/mipmap-mdpi/ic_launcher_home.png
index 961bb7d..16c8ec2 100644
--- a/res/mipmap-mdpi/ic_launcher_home.png
+++ b/res/mipmap-mdpi/ic_launcher_home.png
Binary files differ
diff --git a/res/mipmap-xhdpi/ic_launcher_home.png b/res/mipmap-xhdpi/ic_launcher_home.png
index 46ec2b7..8b2671b 100644
--- a/res/mipmap-xhdpi/ic_launcher_home.png
+++ b/res/mipmap-xhdpi/ic_launcher_home.png
Binary files differ
diff --git a/res/mipmap-xxhdpi/ic_launcher_home.png b/res/mipmap-xxhdpi/ic_launcher_home.png
index d2975a3..43d8b7d 100644
--- a/res/mipmap-xxhdpi/ic_launcher_home.png
+++ b/res/mipmap-xxhdpi/ic_launcher_home.png
Binary files differ
diff --git a/res/values-sw600dp/config.xml b/res/values-sw600dp/config.xml
index a7345a7..eb9af97 100644
--- a/res/values-sw600dp/config.xml
+++ b/res/values-sw600dp/config.xml
@@ -1,7 +1,4 @@
 <resources>
     <bool name="is_tablet">true</bool>
     <bool name="allow_rotation">true</bool>
-
-<!-- DragController -->
-    <integer name="config_flingToDeleteMinVelocity">-1000</integer>
 </resources>
diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml
index 2651fbb..f4a4284 100644
--- a/res/values-sw600dp/dimens.xml
+++ b/res/values-sw600dp/dimens.xml
@@ -19,6 +19,8 @@
     <dimen name="all_apps_grid_view_start_margin">0dp</dimen>
     <dimen name="all_apps_grid_section_text_size">26sp</dimen>
     <dimen name="all_apps_icon_top_bottom_padding">12dp</dimen>
+    <dimen name="all_apps_background_canvas_width">850dp</dimen>
+    <dimen name="all_apps_background_canvas_height">525dp</dimen>
 
 <!-- Cling -->
     <dimen name="cling_migration_logo_height">400dp</dimen>
@@ -28,4 +30,10 @@
     <dimen name="cling_migration_content_margin">64dp</dimen>
     <dimen name="cling_migration_content_width">280dp</dimen>
 
+<!-- Widget tray -->
+    <dimen name="widget_section_indent">56dp</dimen>
+
+
+<!-- DragController -->
+    <dimen name="drag_flingToDeleteMinVelocity">-1000dp</dimen>
 </resources>
diff --git a/res/values-sw720dp/dimens.xml b/res/values-sw720dp/dimens.xml
index d48f9ee..807fab9 100644
--- a/res/values-sw720dp/dimens.xml
+++ b/res/values-sw720dp/dimens.xml
@@ -18,6 +18,8 @@
 <!-- All Apps -->
     <dimen name="all_apps_search_bar_height">54dp</dimen>
     <dimen name="all_apps_icon_top_bottom_padding">14dp</dimen>
+    <dimen name="all_apps_empty_search_message_top_offset">64dp</dimen>
+    <dimen name="all_apps_empty_search_bg_top_offset">180dp</dimen>
 
 <!-- QSB -->
     <dimen name="toolbar_button_vertical_padding">8dip</dimen>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 0add48c..8a7f627 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -39,7 +39,7 @@
     <color name="outline_color">#FFFFFFFF</color>
 
     <!-- Containers -->
-    <color name="container_fastscroll_thumb_inactive_color">#42000000</color>
+    <color name="container_fastscroll_thumb_inactive_color">#009688</color>
     <color name="container_fastscroll_thumb_active_color">#009688</color>
 
     <!-- All Apps -->
@@ -47,7 +47,6 @@
     <color name="all_apps_search_market_button_focused_bg_color">#DDDDDD</color>
 
     <!-- Widgets view -->
-    <color name="widgets_view_fastscroll_thumb_inactive_color">#42FFFFFF</color>
     <color name="widgets_view_section_text_color">#FFFFFF</color>
     <color name="widgets_view_item_text_color">#C4C4C4</color>
     <color name="widgets_cell_color">#263238</color>
diff --git a/res/values/config.xml b/res/values/config.xml
index a1060b5..af4c96d 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -17,7 +17,6 @@
     <bool name="enable_backup">false</bool>
 
 <!-- DragController -->
-    <integer name="config_flingToDeleteMinVelocity">-1500</integer>
     <item type="id" name="drag_event_parity" />
 
 <!-- AllApps & Launcher transitions -->
@@ -71,6 +70,12 @@
          filter the activities shown in the launcher. Can be empty. -->
     <string name="app_filter_class" translatable="false"></string>
 
+    <!-- View ID to use for QSB widget -->
+    <item type="id" name="qsb_widget" />
+
+    <!-- View ID used by cell layout to jail its content -->
+    <item type="id" name="cell_layout_jail_id" />
+
 <!-- Accessibility actions -->
     <item type="id" name="action_remove" />
     <item type="id" name="action_uninstall" />
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index b9fb6e2..4882df6 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -55,9 +55,9 @@
     <!-- Notes: container_bounds_inset - quantum_panel_outer_padding -->
     <dimen name="container_bounds_minus_quantum_panel_padding_inset">4dp</dimen>
 
-    <dimen name="container_fastscroll_thumb_min_width">4dp</dimen>
-    <dimen name="container_fastscroll_thumb_max_width">8dp</dimen>
-    <dimen name="container_fastscroll_thumb_height">64dp</dimen>
+    <dimen name="container_fastscroll_thumb_min_width">5dp</dimen>
+    <dimen name="container_fastscroll_thumb_max_width">9dp</dimen>
+    <dimen name="container_fastscroll_thumb_height">72dp</dimen>
     <dimen name="container_fastscroll_thumb_touch_inset">-24dp</dimen>
     <dimen name="container_fastscroll_popup_size">72dp</dimen>
     <dimen name="container_fastscroll_popup_text_size">48dp</dimen>
@@ -74,6 +74,10 @@
     <dimen name="all_apps_prediction_icon_top_padding">8dp</dimen>
     <dimen name="all_apps_prediction_icon_bottom_padding">18dp</dimen>
     <dimen name="all_apps_list_top_bottom_padding">8dp</dimen>
+    <dimen name="all_apps_empty_search_message_top_offset">40dp</dimen>
+    <dimen name="all_apps_empty_search_bg_top_offset">144dp</dimen>
+    <dimen name="all_apps_background_canvas_width">700dp</dimen>
+    <dimen name="all_apps_background_canvas_height">475dp</dimen>
 
 <!-- Widget tray -->
     <dimen name="widget_container_inset">8dp</dimen>
@@ -85,6 +89,7 @@
     <dimen name="widget_section_icon_size">40dp</dimen>
     <dimen name="widget_section_vertical_padding">8dp</dimen>
     <dimen name="widget_section_horizontal_padding">16dp</dimen>
+    <dimen name="widget_section_indent">0dp</dimen>
 
     <dimen name="widget_row_padding">8dp</dimen>
     <dimen name="widget_row_divider">2dp</dimen>
@@ -111,6 +116,8 @@
          and drop targets like all-apps and folders -->
     <dimen name="drag_elevation">30dp</dimen>
 
+    <dimen name="drag_flingToDeleteMinVelocity">-1500dp</dimen>
+
 <!-- Theme -->
     <dimen name="quantum_panel_outer_padding">4dp</dimen>
 
diff --git a/src/com/android/launcher3/AnotherWindowDropTarget.java b/src/com/android/launcher3/AnotherWindowDropTarget.java
index aff36ae..eaac6be 100644
--- a/src/com/android/launcher3/AnotherWindowDropTarget.java
+++ b/src/com/android/launcher3/AnotherWindowDropTarget.java
@@ -27,7 +27,7 @@
 public class AnotherWindowDropTarget implements DropTarget {
     final Launcher mLauncher;
 
-    AnotherWindowDropTarget (Context context) { mLauncher = (Launcher) context; }
+    public AnotherWindowDropTarget (Context context) { mLauncher = (Launcher) context; }
 
     @Override
     public boolean isDropEnabled() { return true; }
diff --git a/src/com/android/launcher3/AppInfoDropTargetBar.java b/src/com/android/launcher3/AppInfoDropTargetBar.java
new file mode 100644
index 0000000..31ff42a
--- /dev/null
+++ b/src/com/android/launcher3/AppInfoDropTargetBar.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.launcher3.dragndrop.DragController;
+
+public class AppInfoDropTargetBar extends BaseDropTargetBar {
+    private ButtonDropTarget mAppInfoDropTarget;
+
+    public AppInfoDropTargetBar(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public AppInfoDropTargetBar(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        // Get the individual components
+        mAppInfoDropTarget = (ButtonDropTarget) mDropTargetBar.findViewById(R.id.info_target_text);
+
+        mAppInfoDropTarget.setDropTargetBar(this);
+    }
+
+    @Override
+    public void setup(Launcher launcher, DragController dragController) {
+        dragController.addDragListener(this);
+
+        dragController.addDragListener(mAppInfoDropTarget);
+
+        dragController.addDropTarget(mAppInfoDropTarget);
+
+        mAppInfoDropTarget.setLauncher(launcher);
+    }
+
+    @Override
+    public void showDropTarget() {
+        animateDropTargetBarToAlpha(1f, DEFAULT_DRAG_FADE_DURATION);
+    }
+
+    @Override
+    public void hideDropTarget() {
+        animateDropTargetBarToAlpha(0f, DEFAULT_DRAG_FADE_DURATION);
+    }
+
+    private void animateDropTargetBarToAlpha(float alpha, int duration) {
+        animateViewAlpha(mDropTargetBarAnimator, mDropTargetBar, alpha,duration);
+    }
+
+    @Override
+    public void enableAccessibleDrag(boolean enable) {
+        mAppInfoDropTarget.enableAccessibleDrag(enable);
+    }
+}
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index c87074d..6818929 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -1,5 +1,7 @@
 package com.android.launcher3;
 
+import com.android.launcher3.dragndrop.DragLayer;
+
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
@@ -9,6 +11,7 @@
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.view.Gravity;
 import android.widget.FrameLayout;
@@ -19,7 +22,10 @@
     private static final float DIMMED_HANDLE_ALPHA = 0f;
     private static final float RESIZE_THRESHOLD = 0.66f;
 
-    private static Rect sTmpRect = new Rect();
+    private static final Rect sTmpRect = new Rect();
+
+    // Represents the cell size on the grid in the two orientations.
+    private static Point[] sCellSize;
 
     private final Launcher mLauncher;
     private final LauncherAppWidgetHostView mWidgetView;
@@ -341,28 +347,27 @@
     }
 
     public static Rect getWidgetSizeRanges(Launcher launcher, int spanX, int spanY, Rect rect) {
+        if (sCellSize == null) {
+            InvariantDeviceProfile inv = LauncherAppState.getInstance().getInvariantDeviceProfile();
+
+            // Initiate cell sizes.
+            sCellSize = new Point[2];
+            sCellSize[0] = inv.landscapeProfile.getCellSize();
+            sCellSize[1] = inv.portraitProfile.getCellSize();
+        }
+
         if (rect == null) {
             rect = new Rect();
         }
-        Rect landMetrics = Workspace.getCellLayoutMetrics(launcher, CellLayout.LANDSCAPE);
-        Rect portMetrics = Workspace.getCellLayoutMetrics(launcher, CellLayout.PORTRAIT);
         final float density = launcher.getResources().getDisplayMetrics().density;
 
         // Compute landscape size
-        int cellWidth = landMetrics.left;
-        int cellHeight = landMetrics.top;
-        int widthGap = landMetrics.right;
-        int heightGap = landMetrics.bottom;
-        int landWidth = (int) ((spanX * cellWidth + (spanX - 1) * widthGap) / density);
-        int landHeight = (int) ((spanY * cellHeight + (spanY - 1) * heightGap) / density);
+        int landWidth = (int) ((spanX * sCellSize[0].x) / density);
+        int landHeight = (int) ((spanY * sCellSize[0].y) / density);
 
         // Compute portrait size
-        cellWidth = portMetrics.left;
-        cellHeight = portMetrics.top;
-        widthGap = portMetrics.right;
-        heightGap = portMetrics.bottom;
-        int portWidth = (int) ((spanX * cellWidth + (spanX - 1) * widthGap) / density);
-        int portHeight = (int) ((spanY * cellHeight + (spanY - 1) * heightGap) / density);
+        int portWidth = (int) ((spanX * sCellSize[1].x) / density);
+        int portHeight = (int) ((spanY * sCellSize[1].y) / density);
         rect.set(portWidth, landHeight, landWidth, portHeight);
         return rect;
     }
diff --git a/src/com/android/launcher3/BaseDropTargetBar.java b/src/com/android/launcher3/BaseDropTargetBar.java
new file mode 100644
index 0000000..f478a35
--- /dev/null
+++ b/src/com/android/launcher3/BaseDropTargetBar.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.animation.AccelerateInterpolator;
+import android.widget.FrameLayout;
+
+import com.android.launcher3.dragndrop.DragController;
+
+/**
+ * Base class for drop target bars (where you can drop apps to do actions such as uninstall).
+ */
+public abstract class BaseDropTargetBar extends FrameLayout implements DragController.DragListener {
+    protected static final int DEFAULT_DRAG_FADE_DURATION = 175;
+
+    protected View mDropTargetBar;
+
+    protected LauncherViewPropertyAnimator mDropTargetBarAnimator;
+    protected static final AccelerateInterpolator sAccelerateInterpolator =
+            new AccelerateInterpolator();
+    protected boolean mAccessibilityEnabled = false;
+
+    protected boolean mDeferOnDragEnd;
+
+    public BaseDropTargetBar(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public BaseDropTargetBar(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mDropTargetBar = findViewById(R.id.drag_target_bar);
+
+        // Create the various fade animations
+        mDropTargetBar.setAlpha(0f);
+        mDropTargetBarAnimator = new LauncherViewPropertyAnimator(mDropTargetBar);
+        mDropTargetBarAnimator.setInterpolator(sAccelerateInterpolator);
+        mDropTargetBarAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                // Ensure that the view is visible for the animation
+                mDropTargetBar.setVisibility(View.VISIBLE);
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (mDropTargetBar != null) {
+                    AlphaUpdateListener.updateVisibility(mDropTargetBar, mAccessibilityEnabled);
+                }
+            }
+        });
+    }
+
+
+    /**
+     * Convenience method to animate the alpha of a view using hardware layers.
+     */
+    protected void animateViewAlpha(LauncherViewPropertyAnimator animator, View v, float alpha,
+                                  int duration) {
+        if (v == null) {
+            return;
+        }
+
+        animator.cancel();
+        if (Float.compare(v.getAlpha(), alpha) != 0) {
+            if (duration > 0) {
+                animator.alpha(alpha).withLayer().setDuration(duration).start();
+            } else {
+                v.setAlpha(alpha);
+                AlphaUpdateListener.updateVisibility(v, mAccessibilityEnabled);
+            }
+        }
+    }
+
+    /*
+     * DragController.DragListener implementation
+     */
+    @Override
+    public void onDragStart(DragSource source, ItemInfo info, int dragAction) {
+        showDropTarget();
+    }
+
+    /**
+     * This is called to defer hiding the delete drop target until the drop animation has completed,
+     * instead of hiding immediately when the drag has ended.
+     */
+    protected void deferOnDragEnd() {
+        mDeferOnDragEnd = true;
+    }
+
+    @Override
+    public void onDragEnd() {
+        if (!mDeferOnDragEnd) {
+            hideDropTarget();
+        } else {
+            mDeferOnDragEnd = false;
+        }
+    }
+
+    public abstract void showDropTarget();
+
+    public abstract void hideDropTarget();
+
+    public abstract void enableAccessibleDrag(boolean enable);
+
+    public abstract void setup(Launcher launcher, DragController dragController);
+}
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 0fae427..9d713e3 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -92,9 +92,15 @@
             // TODO(winsonc): If we want to animate the section heads while scrolling, we can
             //                initiate that here if the recycler view scroll state is not
             //                RecyclerView.SCROLL_STATE_IDLE.
+
+            onUpdateScrollbar(dy);
         }
     }
 
+    public void reset() {
+        mScrollbar.reattachThumbToScroll();
+    }
+
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
@@ -143,7 +149,7 @@
                 mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
                 break;
         }
-        return mScrollbar.isDragging();
+        return mScrollbar.isDraggingThumb();
     }
 
     public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
@@ -185,12 +191,10 @@
      *   AvailableScrollHeight = Total height of the all items - last page height
      *
      * This assumes that all rows are the same height.
-     *
-     * @param yOffset the offset from the top of the recycler view to start tracking.
      */
-    protected int getAvailableScrollHeight(int rowCount, int rowHeight, int yOffset) {
+    protected int getAvailableScrollHeight(int rowCount, int rowHeight) {
         int visibleHeight = getHeight() - mBackgroundPadding.top - mBackgroundPadding.bottom;
-        int scrollHeight = getPaddingTop() + yOffset + rowCount * rowHeight + getPaddingBottom();
+        int scrollHeight = getPaddingTop() + rowCount * rowHeight + getPaddingBottom();
         int availableScrollHeight = scrollHeight - visibleHeight;
         return availableScrollHeight;
     }
@@ -222,7 +226,7 @@
     @Override
     protected void dispatchDraw(Canvas canvas) {
         super.dispatchDraw(canvas);
-        onUpdateScrollbar();
+        onUpdateScrollbar(0);
         mScrollbar.draw(canvas);
     }
 
@@ -234,24 +238,21 @@
      * @param scrollPosState the current scroll position
      * @param rowCount the number of rows, used to calculate the total scroll height (assumes that
      *                 all rows are the same height)
-     * @param yOffset the offset to start tracking in the recycler view (only used for all apps)
      */
     protected void synchronizeScrollBarThumbOffsetToViewScroll(ScrollPositionState scrollPosState,
-            int rowCount, int yOffset) {
-        int availableScrollHeight = getAvailableScrollHeight(rowCount, scrollPosState.rowHeight,
-                yOffset);
-        int availableScrollBarHeight = getAvailableScrollBarHeight();
-
+            int rowCount) {
         // Only show the scrollbar if there is height to be scrolled
+        int availableScrollBarHeight = getAvailableScrollBarHeight();
+        int availableScrollHeight = getAvailableScrollHeight(rowCount, scrollPosState.rowHeight);
         if (availableScrollHeight <= 0) {
-            mScrollbar.setScrollbarThumbOffset(-1, -1);
+            mScrollbar.setThumbOffset(-1, -1);
             return;
         }
 
         // Calculate the current scroll position, the scrollY of the recycler view accounts for the
         // view padding, while the scrollBarY is drawn right up to the background padding (ignoring
         // padding)
-        int scrollY = getPaddingTop() + yOffset +
+        int scrollY = getPaddingTop() +
                 (scrollPosState.rowIndex * scrollPosState.rowHeight) - scrollPosState.rowTopOffset;
         int scrollBarY = mBackgroundPadding.top +
                 (int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
@@ -261,9 +262,16 @@
         if (Utilities.isRtl(getResources())) {
             scrollBarX = mBackgroundPadding.left;
         } else {
-            scrollBarX = getWidth() - mBackgroundPadding.right - mScrollbar.getWidth();
+            scrollBarX = getWidth() - mBackgroundPadding.right - mScrollbar.getThumbWidth();
         }
-        mScrollbar.setScrollbarThumbOffset(scrollBarX, scrollBarY);
+        mScrollbar.setThumbOffset(scrollBarX, scrollBarY);
+    }
+
+    /**
+     * Returns whether fast scrolling is supported in the current state.
+     */
+    protected boolean supportsFastScrolling() {
+        return true;
     }
 
     /**
@@ -276,10 +284,15 @@
      * Updates the bounds for the scrollbar.
      * <p>Override in each subclass of this base class.
      */
-    public abstract void onUpdateScrollbar();
+    public abstract void onUpdateScrollbar(int dy);
 
     /**
      * <p>Override in each subclass of this base class.
      */
     public void onFastScrollCompleted() {}
+
+    /**
+     * Returns information about the item that the recycler view is currently scrolled to.
+     */
+    protected abstract void getCurScrollState(ScrollPositionState stateOut);
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
index 2c4184d..32ea576 100644
--- a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
+++ b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
@@ -23,6 +23,7 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
+import android.graphics.Path;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.view.MotionEvent;
@@ -51,14 +52,21 @@
     private int mThumbActiveColor;
     @Thunk Point mThumbOffset = new Point(-1, -1);
     @Thunk Paint mThumbPaint;
-    private Paint mTrackPaint;
     private int mThumbMinWidth;
     private int mThumbMaxWidth;
     @Thunk int mThumbWidth;
     @Thunk int mThumbHeight;
+    private int mThumbCurvature;
+    private Path mThumbPath = new Path();
+    private Paint mTrackPaint;
+    private int mTrackWidth;
+    private float mLastTouchY;
     // The inset is the buffer around which a point will still register as a click on the scrollbar
     private int mTouchInset;
     private boolean mIsDragging;
+    private boolean mIsThumbDetached;
+    private boolean mCanThumbDetach;
+    private boolean mIgnoreDragGesture;
 
     // This is the offset from the top of the scrollbar when the user first starts touching.  To
     // prevent jumping, this offset is applied as the user scrolls.
@@ -72,51 +80,74 @@
         mPopup = new BaseRecyclerViewFastScrollPopup(rv, res);
         mTrackPaint = new Paint();
         mTrackPaint.setColor(rv.getFastScrollerTrackColor(Color.BLACK));
-        mTrackPaint.setAlpha(0);
+        mTrackPaint.setAlpha(MAX_TRACK_ALPHA);
         mThumbInactiveColor = rv.getFastScrollerThumbInactiveColor(
                 res.getColor(R.color.container_fastscroll_thumb_inactive_color));
         mThumbActiveColor = res.getColor(R.color.container_fastscroll_thumb_active_color);
         mThumbPaint = new Paint();
+        mThumbPaint.setAntiAlias(true);
         mThumbPaint.setColor(mThumbInactiveColor);
+        mThumbPaint.setStyle(Paint.Style.FILL);
         mThumbWidth = mThumbMinWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_min_width);
         mThumbMaxWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_max_width);
         mThumbHeight = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_height);
+        mThumbCurvature = mThumbMaxWidth - mThumbMinWidth;
         mTouchInset = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_touch_inset);
     }
 
-    public void setScrollbarThumbOffset(int x, int y) {
+    public void setDetachThumbOnFastScroll() {
+        mCanThumbDetach = true;
+    }
+
+    public void reattachThumbToScroll() {
+        mIsThumbDetached = false;
+    }
+
+    public void setThumbOffset(int x, int y) {
         if (mThumbOffset.x == x && mThumbOffset.y == y) {
             return;
         }
-        mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight());
+        mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
+                mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
         mThumbOffset.set(x, y);
-        mInvalidateRect.union(new Rect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth,
-                mRv.getHeight()));
+        updateThumbPath();
+        mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
+                mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
         mRv.invalidate(mInvalidateRect);
     }
 
-    // Setter/getter for the search bar width for animations
-    public void setWidth(int width) {
-        mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight());
+    public Point getThumbOffset() {
+        return mThumbOffset;
+    }
+
+    // Setter/getter for the thumb bar width for animations
+    public void setThumbWidth(int width) {
+        mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
+                mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
         mThumbWidth = width;
-        mInvalidateRect.union(new Rect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth,
-                mRv.getHeight()));
+        updateThumbPath();
+        mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
+                mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
         mRv.invalidate(mInvalidateRect);
     }
 
-    public int getWidth() {
+    public int getThumbWidth() {
         return mThumbWidth;
     }
 
-    // Setter/getter for the track background alpha for animations
-    public void setTrackAlpha(int alpha) {
-        mTrackPaint.setAlpha(alpha);
-        mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight());
+    // Setter/getter for the track bar width for animations
+    public void setTrackWidth(int width) {
+        mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, 0, mThumbOffset.x + mThumbWidth,
+                mRv.getHeight());
+        mTrackWidth = width;
+        updateThumbPath();
+        mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, 0, mThumbOffset.x + mThumbWidth,
+                mRv.getHeight());
         mRv.invalidate(mInvalidateRect);
     }
 
-    public int getTrackAlpha() {
-        return mTrackPaint.getAlpha();
+    public int getTrackWidth() {
+        return mTrackWidth;
     }
 
     public int getThumbHeight() {
@@ -127,10 +158,18 @@
         return mThumbMaxWidth;
     }
 
-    public boolean isDragging() {
+    public float getLastTouchY() {
+        return mLastTouchY;
+    }
+
+    public boolean isDraggingThumb() {
         return mIsDragging;
     }
 
+    public boolean isThumbDetached() {
+        return mIsThumbDetached;
+    }
+
     /**
      * Handles the touch event and determines whether to show the fast scroller (or updates it if
      * it is already showing).
@@ -142,16 +181,22 @@
         int y = (int) ev.getY();
         switch (action) {
             case MotionEvent.ACTION_DOWN:
-                if (isNearPoint(downX, downY)) {
+                if (isNearThumb(downX, downY)) {
                     mTouchOffset = downY - mThumbOffset.y;
                 }
                 break;
             case MotionEvent.ACTION_MOVE:
-                // Check if we should start scrolling
-                if (!mIsDragging && isNearPoint(downX, downY) &&
+                // Check if we should start scrolling, but ignore this fastscroll gesture if we have
+                // exceeded some fixed movement
+                mIgnoreDragGesture |= Math.abs(y - downY) > config.getScaledPagingTouchSlop();
+                if (!mIsDragging && !mIgnoreDragGesture && mRv.supportsFastScrolling() &&
+                        isNearThumb(downX, lastY) &&
                         Math.abs(y - downY) > config.getScaledTouchSlop()) {
                     mRv.getParent().requestDisallowInterceptTouchEvent(true);
                     mIsDragging = true;
+                    if (mCanThumbDetach) {
+                        mIsThumbDetached = true;
+                    }
                     mTouchOffset += (lastY - downY);
                     mPopup.animateVisibility(true);
                     animateScrollbar(true);
@@ -166,11 +211,14 @@
                     mPopup.setSectionName(sectionName);
                     mPopup.animateVisibility(!sectionName.isEmpty());
                     mRv.invalidate(mPopup.updateFastScrollerBounds(mRv, lastY));
+                    mLastTouchY = boundedY;
                 }
                 break;
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
                 mTouchOffset = 0;
+                mLastTouchY = 0;
+                mIgnoreDragGesture = false;
                 if (mIsDragging) {
                     mIsDragging = false;
                     mPopup.animateVisibility(false);
@@ -189,8 +237,7 @@
         if (mTrackPaint.getAlpha() > 0) {
             canvas.drawRect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight(), mTrackPaint);
         }
-        canvas.drawRect(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
-                mThumbOffset.y + mThumbHeight, mThumbPaint);
+        canvas.drawPath(mThumbPath, mThumbPaint);
 
         // Draw the popup
         mPopup.draw(canvas);
@@ -203,30 +250,49 @@
         if (mScrollbarAnimator != null) {
             mScrollbarAnimator.cancel();
         }
-        ObjectAnimator trackAlphaAnim = ObjectAnimator.ofInt(this, "trackAlpha",
-                isScrolling ? MAX_TRACK_ALPHA : 0);
-        ObjectAnimator thumbWidthAnim = ObjectAnimator.ofInt(this, "width",
-                isScrolling ? mThumbMaxWidth : mThumbMinWidth);
-        ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(),
-                mThumbPaint.getColor(), isScrolling ? mThumbActiveColor : mThumbInactiveColor);
-        colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animator) {
-                mThumbPaint.setColor((Integer) animator.getAnimatedValue());
-                mRv.invalidate(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
-                        mThumbOffset.y + mThumbHeight);
-            }
-        });
+
         mScrollbarAnimator = new AnimatorSet();
-        mScrollbarAnimator.playTogether(trackAlphaAnim, thumbWidthAnim, colorAnimation);
+        ObjectAnimator trackWidthAnim = ObjectAnimator.ofInt(this, "trackWidth",
+                isScrolling ? mThumbMaxWidth : mThumbMinWidth);
+        ObjectAnimator thumbWidthAnim = ObjectAnimator.ofInt(this, "thumbWidth",
+                isScrolling ? mThumbMaxWidth : mThumbMinWidth);
+        mScrollbarAnimator.playTogether(trackWidthAnim, thumbWidthAnim);
+        if (mThumbActiveColor != mThumbInactiveColor) {
+            ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(),
+                    mThumbPaint.getColor(), isScrolling ? mThumbActiveColor : mThumbInactiveColor);
+            colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animator) {
+                    mThumbPaint.setColor((Integer) animator.getAnimatedValue());
+                    mRv.invalidate(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
+                            mThumbOffset.y + mThumbHeight);
+                }
+            });
+            mScrollbarAnimator.play(colorAnimation);
+        }
         mScrollbarAnimator.setDuration(SCROLL_BAR_VIS_DURATION);
         mScrollbarAnimator.start();
     }
 
     /**
+     * Updates the path for the thumb drawable.
+     */
+    private void updateThumbPath() {
+        mThumbCurvature = mThumbMaxWidth - mThumbWidth;
+        mThumbPath.reset();
+        mThumbPath.moveTo(mThumbOffset.x + mThumbWidth, mThumbOffset.y);                    // tr
+        mThumbPath.lineTo(mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);     // br
+        mThumbPath.lineTo(mThumbOffset.x, mThumbOffset.y + mThumbHeight);                   // bl
+        mThumbPath.cubicTo(mThumbOffset.x, mThumbOffset.y + mThumbHeight,
+                mThumbOffset.x - mThumbCurvature, mThumbOffset.y + mThumbHeight / 2,
+                mThumbOffset.x, mThumbOffset.y);                                            // bl2tl
+        mThumbPath.close();
+    }
+
+    /**
      * Returns whether the specified points are near the scroll bar bounds.
      */
-    private boolean isNearPoint(int x, int y) {
+    private boolean isNearThumb(int x, int y) {
         mTmpRect.set(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
                 mThumbOffset.y + mThumbHeight);
         mTmpRect.inset(mTouchInset, mTouchInset);
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 5070878..c8af600 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -144,7 +144,7 @@
         }
 
         mLongPressHelper = new CheckLongPressHelper(this);
-        mStylusEventHelper = new StylusEventHelper(this);
+        mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
 
         mOutlineHelper = HolographicOutlineHelper.obtain(getContext());
         if (mCustomShadowsEnabled) {
@@ -270,7 +270,7 @@
         boolean result = super.onTouchEvent(event);
 
         // Check for a stylus button press, if it occurs cancel any long press checks.
-        if (mStylusEventHelper.checkAndPerformStylusEvent(event)) {
+        if (mStylusEventHelper.onMotionEvent(event)) {
             mLongPressHelper.cancelLongPress();
             result = true;
         }
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index f97179c..703de53 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -38,6 +38,9 @@
 import android.view.animation.LinearInterpolator;
 import android.widget.TextView;
 
+import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.util.Thunk;
 
 /**
@@ -46,11 +49,11 @@
 public abstract class ButtonDropTarget extends TextView
         implements DropTarget, DragController.DragListener, OnClickListener {
 
-    private static int DRAG_VIEW_DROP_DURATION = 285;
+    private static final int DRAG_VIEW_DROP_DURATION = 285;
 
     protected Launcher mLauncher;
     private int mBottomDragPadding;
-    protected SearchDropTargetBar mSearchDropTargetBar;
+    protected BaseDropTargetBar mDropTargetBar;
 
     /** Whether this drop target is active for the current drag */
     protected boolean mActive;
@@ -103,8 +106,8 @@
         mLauncher = launcher;
     }
 
-    public void setSearchDropTargetBar(SearchDropTargetBar searchDropTargetBar) {
-        mSearchDropTargetBar = searchDropTargetBar;
+    public void setDropTargetBar(BaseDropTargetBar dropTargetBar) {
+        mDropTargetBar = dropTargetBar;
     }
 
     @Override
@@ -227,13 +230,13 @@
         final Rect to = getIconRect(d.dragView.getMeasuredWidth(), d.dragView.getMeasuredHeight(),
                 width, height);
         final float scale = (float) to.width() / from.width();
-        mSearchDropTargetBar.deferOnDragEnd();
+        mDropTargetBar.deferOnDragEnd();
 
         Runnable onAnimationEndRunnable = new Runnable() {
             @Override
             public void run() {
                 completeDrop(d);
-                mSearchDropTargetBar.onDragEnd();
+                mDropTargetBar.onDragEnd();
                 mLauncher.exitSpringLoadedDragModeDelayed(true, 0, null);
             }
         };
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index cd005e5..b8b4144 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -24,7 +24,6 @@
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.Resources;
-import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -36,6 +35,7 @@
 import android.graphics.drawable.TransitionDrawable;
 import android.os.Build;
 import android.os.Parcelable;
+import android.os.PowerManager;
 import android.support.v4.view.ViewCompat;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -46,6 +46,7 @@
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.DecelerateInterpolator;
+import android.widget.Toast;
 
 import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler;
 import com.android.launcher3.FolderIcon.FolderRingAnimator;
@@ -54,6 +55,7 @@
 import com.android.launcher3.accessibility.FolderAccessibilityHelper;
 import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
 import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.util.ParcelableSparseArray;
 import com.android.launcher3.util.Thunk;
 
 import java.util.ArrayList;
@@ -85,6 +87,7 @@
     private int mMaxGap;
     private boolean mDropPending = false;
     private boolean mIsDragTarget = true;
+    private boolean mJailContent = true;
 
     // These are temporary variables to prevent having to allocate a new object just to
     // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
@@ -188,7 +191,6 @@
         mLauncher = (Launcher) context;
 
         DeviceProfile grid = mLauncher.getDeviceProfile();
-        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
 
         mCellWidth = mCellHeight = -1;
         mFixedCellWidth = mFixedCellHeight = -1;
@@ -202,10 +204,7 @@
         mPreviousReorderDirection[0] = INVALID_DIRECTION;
         mPreviousReorderDirection[1] = INVALID_DIRECTION;
 
-        a.recycle();
-
         setAlwaysDrawnWithCacheEnabled(false);
-
         final Resources res = getResources();
         mHotseatScale = (float) grid.hotseatIconSizePx / grid.iconSizePx;
 
@@ -277,7 +276,7 @@
         mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
                 mCountX, mCountY);
 
-        mStylusEventHelper = new StylusEventHelper(this);
+        mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
 
         mTouchFeedbackView = new ClickShadowView(context);
         addView(mTouchFeedbackView);
@@ -339,7 +338,7 @@
         // enabled to allow rearranging the different home screens. So check what mode
         // the workspace is in, and only perform stylus button presses while in overview mode.
         if (mLauncher.mWorkspace.isInOverviewMode()
-                && mStylusEventHelper.checkAndPerformStylusEvent(ev)) {
+                && mStylusEventHelper.onMotionEvent(ev)) {
             return true;
         }
         return handled;
@@ -405,7 +404,7 @@
         mIsDragTarget = false;
     }
 
-    boolean isDragTarget() {
+    public boolean isDragTarget() {
         return mIsDragTarget;
     }
 
@@ -425,7 +424,33 @@
         }
     }
 
-    boolean getIsDragOverlapping() {
+    public void disableJailContent() {
+        mJailContent = false;
+    }
+
+    @Override
+    protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
+        if (mJailContent) {
+            ParcelableSparseArray jail = getJailedArray(container);
+            super.dispatchSaveInstanceState(jail);
+            container.put(R.id.cell_layout_jail_id, jail);
+        } else {
+            super.dispatchSaveInstanceState(container);
+        }
+    }
+
+    @Override
+    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
+        super.dispatchRestoreInstanceState(mJailContent ? getJailedArray(container) : container);
+    }
+
+    private ParcelableSparseArray getJailedArray(SparseArray<Parcelable> container) {
+        final Parcelable parcelable = container.get(R.id.cell_layout_jail_id);
+        return parcelable instanceof ParcelableSparseArray ?
+                (ParcelableSparseArray) parcelable : new ParcelableSparseArray();
+    }
+
+    public boolean getIsDragOverlapping() {
         return mIsDragOverlapping;
     }
 
@@ -2118,8 +2143,18 @@
             }
             ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
             a = va;
-            va.setRepeatMode(ValueAnimator.REVERSE);
-            va.setRepeatCount(ValueAnimator.INFINITE);
+
+            // Animations are disabled in power save mode, causing the repeated animation to jump
+            // spastically between beginning and end states. Since this looks bad, we don't repeat
+            // the animation in power save mode.
+            PowerManager powerManager = (PowerManager) getContext()
+                    .getSystemService(Context.POWER_SERVICE);
+            boolean powerSaverOn = Utilities.ATLEAST_LOLLIPOP && powerManager.isPowerSaveMode();
+            if (!powerSaverOn) {
+                va.setRepeatMode(ValueAnimator.REVERSE);
+                va.setRepeatCount(ValueAnimator.INFINITE);
+            }
+
             va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION);
             va.setStartDelay((int) (Math.random() * 60));
             va.addUpdateListener(new AnimatorUpdateListener() {
@@ -2650,6 +2685,7 @@
             LayoutParams lp = (LayoutParams) child.getLayoutParams();
             lp.dropped = true;
             child.requestLayout();
+            markCellsAsOccupiedForView(child);
         }
     }
 
@@ -2802,10 +2838,10 @@
 
         // X coordinate of the view in the layout.
         @ViewDebug.ExportedProperty
-        int x;
+        public int x;
         // Y coordinate of the view in the layout.
         @ViewDebug.ExportedProperty
-        int y;
+        public int y;
 
         boolean dropped;
 
@@ -2931,6 +2967,26 @@
         return Utilities.findVacantCell(outXY, spanX, spanY, mCountX, mCountY, mOccupied);
     }
 
+    /**
+     * Returns whether an item can be placed in this CellLayout (after rearranging and/or resizing
+     * if necessary).
+     */
+    public boolean hasReorderSolution(ItemInfo itemInfo) {
+        int[] cellPoint = new int[2];
+        // Check for a solution starting at every cell.
+        for (int cellX = 0; cellX < getCountX(); cellX++) {
+            for (int cellY = 0; cellY < getCountY(); cellY++) {
+                cellToPoint(cellX, cellY, cellPoint);
+                if (findReorderSolution(cellPoint[0], cellPoint[1], itemInfo.minSpanX,
+                        itemInfo.minSpanY, itemInfo.spanX, itemInfo.spanY, mDirectionVector, null,
+                        true, new ItemConfiguration()).isSolution) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
         int x2 = x + spanX - 1;
         int y2 = y + spanY - 1;
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index bf5bc17..76e14f5 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -19,11 +19,11 @@
 import android.animation.TimeInterpolator;
 import android.content.Context;
 import android.graphics.PointF;
-import android.os.AsyncTask;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.animation.AnimationUtils;
 
+import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.util.FlingAnimation;
 import com.android.launcher3.util.Thunk;
 
@@ -70,40 +70,11 @@
      * @return true if the item was removed.
      */
     public static boolean removeWorkspaceOrFolderItem(Launcher launcher, ItemInfo item, View view) {
-        if (item instanceof ShortcutInfo) {
-            LauncherModel.deleteItemFromDatabase(launcher, item);
-        } else if (item instanceof FolderInfo) {
-            FolderInfo folder = (FolderInfo) item;
-            launcher.removeFolder(folder);
-            LauncherModel.deleteFolderContentsFromDatabase(launcher, folder);
-        } else if (item instanceof LauncherAppWidgetInfo) {
-            final LauncherAppWidgetInfo widget = (LauncherAppWidgetInfo) item;
-
-            // Remove the widget from the workspace
-            launcher.removeAppWidget(widget);
-            LauncherModel.deleteItemFromDatabase(launcher, widget);
-
-            final LauncherAppWidgetHost appWidgetHost = launcher.getAppWidgetHost();
-
-            if (appWidgetHost != null && !widget.isCustomWidget()
-                    && widget.isWidgetIdValid()) {
-                // Deleting an app widget ID is a void call but writes to disk before returning
-                // to the caller...
-                new AsyncTask<Void, Void, Void>() {
-                    public Void doInBackground(Void ... args) {
-                        appWidgetHost.deleteAppWidgetId(widget.appWidgetId);
-                        return null;
-                    }
-                }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
-            }
-        } else {
-            return false;
-        }
-
-        if (view != null) {
-            launcher.getWorkspace().removeWorkspaceItem(view);
-            launcher.getWorkspace().stripEmptyScreens();
-        }
+        // Remove the item from launcher and the db, we can ignore the containerInfo in this call
+        // because we already remove the drag view from the folder (if the drag originated from
+        // a folder) in Folder.beginDrag()
+        launcher.removeItem(view, null, item, true /* deleteFromDb */);
+        launcher.getWorkspace().stripEmptyScreens();
         return true;
     }
 
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 9b4642d..69ef826 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -280,6 +280,18 @@
         return bounds;
     }
 
+    public Point getCellSize() {
+        Point result = new Point();
+        // Since we are only concerned with the overall padding, layout direction does
+        // not matter.
+        Rect padding = getWorkspacePadding(false /* isLayoutRtl */ );
+        result.x = calculateCellWidth(availableWidthPx - padding.left - padding.right,
+                inv.numColumns);
+        result.y = calculateCellHeight(availableHeightPx - padding.top - padding.bottom,
+                inv.numRows);
+        return result;
+    }
+
     /** Returns the workspace padding in the specified orientation */
     Rect getWorkspacePadding(boolean isLayoutRtl) {
         Rect searchBarBounds = getSearchBarBounds(isLayoutRtl);
@@ -360,7 +372,7 @@
      * When {@code true}, hotseat is on the bottom row when in landscape mode.
      * If {@code false}, hotseat is on the right column when in landscape mode.
      */
-    boolean isVerticalBarLayout() {
+    public boolean isVerticalBarLayout() {
         return isLandscape && transposeLayoutWithOrientation;
     }
 
@@ -385,29 +397,15 @@
 
         // Layout the search bar space
         View searchBar = launcher.getSearchDropTargetBar();
-        lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
-        if (hasVerticalBarLayout) {
-            // Vertical search bar space -- The search bar is fixed in the layout to be on the left
-            //                              of the screen regardless of RTL
-            lp.gravity = Gravity.LEFT;
-            lp.width = searchBarSpaceHeightPx;
-
-            LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
-            targets.setOrientation(LinearLayout.VERTICAL);
-            FrameLayout.LayoutParams targetsLp = (FrameLayout.LayoutParams) targets.getLayoutParams();
-            targetsLp.gravity = Gravity.TOP;
-            targetsLp.height = LayoutParams.WRAP_CONTENT;
-
-        } else {
-            // Horizontal search bar space
-            lp.gravity = Gravity.TOP;
-            lp.height = searchBarSpaceHeightPx;
-
-            LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
-            targets.getLayoutParams().width = searchBarSpaceWidthPx;
-        }
+        lp = getDropTargetBarLayoutParams(hasVerticalBarLayout, searchBar, Gravity.TOP);
         searchBar.setLayoutParams(lp);
 
+        // Layout the app info bar space
+        View appInfoBar = launcher.getAppInfoDropTargetBar();
+        lp = getDropTargetBarLayoutParams(hasVerticalBarLayout, appInfoBar, Gravity.BOTTOM);
+        lp.bottomMargin = hotseatBarHeightPx;
+        appInfoBar.setLayoutParams(lp);
+
         // Layout the workspace
         PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace);
         lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();
@@ -466,7 +464,6 @@
         // Layout the Overview Mode
         ViewGroup overviewMode = launcher.getOverviewPanel();
         if (overviewMode != null) {
-            int overviewButtonBarHeight = getOverviewModeButtonBarHeight();
             lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
             lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
 
@@ -475,7 +472,7 @@
             int maxWidth = totalItemWidth + (visibleChildCount-1) * overviewModeBarSpacerWidthPx;
 
             lp.width = Math.min(availableWidthPx, maxWidth);
-            lp.height = overviewButtonBarHeight;
+            lp.height = getOverviewModeButtonBarHeight();
             overviewMode.setLayoutParams(lp);
 
             if (lp.width > totalItemWidth && visibleChildCount > 1) {
@@ -504,6 +501,31 @@
         }
     }
 
+    private FrameLayout.LayoutParams getDropTargetBarLayoutParams(boolean hasVerticalBarLayout,
+            View dropTargetBar, int verticalGravity) {
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) dropTargetBar.getLayoutParams();
+        if (hasVerticalBarLayout) {
+            // Vertical drop target bar space -- The drop target bar is fixed in the layout to be on
+            //                                   the left of the screen regardless of RTL
+            lp.gravity = Gravity.LEFT;
+            lp.width = searchBarSpaceHeightPx;
+
+            LinearLayout targets = (LinearLayout) dropTargetBar.findViewById(R.id.drag_target_bar);
+            targets.setOrientation(LinearLayout.VERTICAL);
+            FrameLayout.LayoutParams targetsLp = (FrameLayout.LayoutParams) targets.getLayoutParams();
+            targetsLp.gravity = verticalGravity;
+            targetsLp.height = LayoutParams.WRAP_CONTENT;
+        } else {
+            // Horizontal drop target bar space
+            lp.gravity = verticalGravity;
+            lp.height = searchBarSpaceHeightPx;
+
+            LinearLayout targets = (LinearLayout) dropTargetBar.findViewById(R.id.drag_target_bar);
+            targets.getLayoutParams().width = searchBarSpaceWidthPx;
+        }
+        return lp;
+    }
+
     private int getCurrentWidth() {
         return isLandscape
                 ? Math.max(widthPx, heightPx)
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index 3748464..592cd32 100644
--- a/src/com/android/launcher3/DropTarget.java
+++ b/src/com/android/launcher3/DropTarget.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3;
 
+import com.android.launcher3.dragndrop.DragView;
+
 import android.graphics.PointF;
 import android.graphics.Rect;
 
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
index 3751b88..be4d04f 100644
--- a/src/com/android/launcher3/FocusHelper.java
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -73,7 +73,6 @@
                         KeyEvent.keyCodeToString(keyCode)));
             }
 
-
             if (!(v.getParent() instanceof ShortcutAndWidgetContainer)) {
                 if (ProviderConfig.IS_DOGFOOD_BUILD) {
                     throw new IllegalStateException("Parent of the focused item is not supported.");
@@ -186,7 +185,8 @@
             return consume;
         }
 
-        DeviceProfile profile = ((Launcher) v.getContext()).getDeviceProfile();
+        final Launcher launcher = (Launcher) v.getContext();
+        final DeviceProfile profile = launcher.getDeviceProfile();
 
         if (DEBUG) {
             Log.v(TAG, String.format(
@@ -195,11 +195,11 @@
         }
 
         // Initialize the variables.
+        final Workspace workspace = (Workspace) v.getRootView().findViewById(R.id.workspace);
         final ShortcutAndWidgetContainer hotseatParent = (ShortcutAndWidgetContainer) v.getParent();
         final CellLayout hotseatLayout = (CellLayout) hotseatParent.getParent();
-        Hotseat hotseat = (Hotseat) hotseatLayout.getParent();
 
-        Workspace workspace = (Workspace) v.getRootView().findViewById(R.id.workspace);
+        final ItemInfo itemInfo = (ItemInfo) v.getTag();
         int pageIndex = workspace.getNextPage();
         int pageCount = workspace.getChildCount();
         int countX = -1;
@@ -241,7 +241,15 @@
         } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
                 profile.isVerticalBarLayout()) {
             keyCode = KeyEvent.KEYCODE_PAGE_DOWN;
-        }else {
+        } else if (isUninstallKeyChord(e)) {
+            matrix = FocusLogic.createSparseMatrix(iconLayout);
+            if (UninstallDropTarget.supportsDrop(launcher, itemInfo)) {
+                UninstallDropTarget.startUninstallActivity(launcher, itemInfo);
+            }
+        } else if (isDeleteKeyChord(e)) {
+            matrix = FocusLogic.createSparseMatrix(iconLayout);
+            launcher.removeItem(v, null, itemInfo, true /* deleteFromDb */);
+        } else {
             // For other KEYCODE_DPAD_LEFT and KEYCODE_DPAD_RIGHT navigation, do not use the
             // matrix extended with hotseat.
             matrix = FocusLogic.createSparseMatrix(hotseatLayout);
@@ -302,6 +310,7 @@
         final ViewGroup tabs = (ViewGroup) dragLayer.findViewById(R.id.search_drop_target_bar);
         final Hotseat hotseat = (Hotseat) dragLayer.findViewById(R.id.hotseat);
 
+        final ItemInfo itemInfo = (ItemInfo) v.getTag();
         final int iconIndex = parent.indexOfChild(v);
         final int pageIndex = workspace.indexOfChild(iconLayout);
         final int pageCount = workspace.getChildCount();
@@ -326,9 +335,14 @@
                     profile.inv.hotseatAllAppsRank,
                     !hotseat.hasIcons() /* ignore all apps icon, unless there are no other icons */);
             countX = countX + 1;
-        } else if (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) {
-            workspace.removeWorkspaceItem(v);
-            return consume;
+        } else if (isUninstallKeyChord(e)) {
+            matrix = FocusLogic.createSparseMatrix(iconLayout);
+            if (UninstallDropTarget.supportsDrop(launcher, itemInfo)) {
+                UninstallDropTarget.startUninstallActivity(launcher, itemInfo);
+            }
+        } else if (isDeleteKeyChord(e)) {
+            matrix = FocusLogic.createSparseMatrix(iconLayout);
+            launcher.removeItem(v, null, itemInfo, true /* deleteFromDb */);
         } else {
             matrix = FocusLogic.createSparseMatrix(iconLayout);
         }
@@ -458,4 +472,22 @@
                 break;
         }
     }
+
+    /**
+     * Returns whether the key event represents a valid uninstall key chord.
+     */
+    private static boolean isUninstallKeyChord(KeyEvent event) {
+        int keyCode = event.getKeyCode();
+        return (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) &&
+                event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON);
+    }
+
+    /**
+     * Returns whether the key event represents a valid delete key chord.
+     */
+    private static boolean isDeleteKeyChord(KeyEvent event) {
+        int keyCode = event.getKeyCode();
+        return (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) &&
+                event.hasModifiers(KeyEvent.META_CTRL_ON);
+    }
 }
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index 4af56ac..ff65d30 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -36,6 +36,7 @@
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.ActionMode;
+import android.view.FocusFinder;
 import android.view.KeyEvent;
 import android.view.Menu;
 import android.view.MenuItem;
@@ -52,11 +53,13 @@
 import android.widget.TextView;
 
 import com.android.launcher3.CellLayout.CellInfo;
-import com.android.launcher3.DragController.DragListener;
 import com.android.launcher3.FolderInfo.FolderListener;
 import com.android.launcher3.UninstallDropTarget.UninstallSource;
 import com.android.launcher3.Workspace.ItemOperator;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.AccessibilityDragSource;
+import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.dragndrop.DragController.DragListener;
+import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.UiThreadCircularReveal;
 
@@ -316,7 +319,7 @@
 
         if (commit) {
             sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
-                    String.format(getContext().getString(R.string.folder_renamed), newTitle));
+                    getContext().getString(R.string.folder_renamed, newTitle));
         }
         // In order to clear the focus from the text field, we set the focus on ourself. This
         // ensures that every time the field is clicked, focus is gained, giving reliable behavior.
@@ -356,11 +359,26 @@
     }
 
     @Override
+    protected void onAttachedToWindow() {
+        // requestFocus() causes the focus onto the folder itself, which doesn't cause visual
+        // effect but the next arrow key can start the keyboard focus inside of the folder, not
+        // the folder itself.
+        requestFocus();
+        super.onAttachedToWindow();
+    }
+
+    @Override
     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
         // When the folder gets focus, we don't want to announce the list of items.
         return true;
     }
 
+    @Override
+    public View focusSearch(int direction) {
+        // When the folder is focused, further focus search should be within the folder contents.
+        return FocusFinder.getInstance().findNextFocus(this, null, direction);
+    }
+
     /**
      * @return the FolderInfo object associated with this folder
      */
@@ -888,7 +906,7 @@
 
     @Override
     public boolean supportsAppInfoDropTarget() {
-        return false;
+        return true;
     }
 
     @Override
@@ -1105,32 +1123,29 @@
             public void run() {
                 CellLayout cellLayout = mLauncher.getCellLayout(mInfo.container, mInfo.screenId);
 
-                View child = null;
-                // Move the item from the folder to the workspace, in the position of the folder
-                if (getItemCount() == 1) {
-                    ShortcutInfo finalItem = mInfo.contents.get(0);
-                    child = mLauncher.createShortcut(cellLayout, finalItem);
-                    LauncherModel.addOrMoveItemInDatabase(mLauncher, finalItem, mInfo.container,
-                            mInfo.screenId, mInfo.cellX, mInfo.cellY);
-                }
+                // Remove the folder
                 if (getItemCount() <= 1) {
-                    // Remove the folder
-                    LauncherModel.deleteItemFromDatabase(mLauncher, mInfo);
-                    if (cellLayout != null) {
-                        // b/12446428 -- sometimes the cell layout has already gone away?
-                        cellLayout.removeView(mFolderIcon);
-                    }
+                    mLauncher.removeItem(mFolderIcon, null, mInfo, true /* deleteFromDb */);
                     if (mFolderIcon instanceof DropTarget) {
                         mDragController.removeDropTarget((DropTarget) mFolderIcon);
                     }
-                    mLauncher.removeFolder(mInfo);
                 }
-                // We add the child after removing the folder to prevent both from existing at
-                // the same time in the CellLayout.  We need to add the new item with addInScreenFromBind()
-                // to ensure that hotseat items are placed correctly.
-                if (child != null) {
+
+                // Move the item from the folder to the workspace, in the position of the folder
+                if (getItemCount() == 1) {
+                    ShortcutInfo finalItem = mInfo.contents.get(0);
+                    View child = mLauncher.createShortcut(cellLayout, finalItem);
+                    LauncherModel.addOrMoveItemInDatabase(mLauncher, finalItem, mInfo.container,
+                            mInfo.screenId, mInfo.cellX, mInfo.cellY);
+
+                    // We add the child after removing the folder to prevent both from existing at
+                    // the same time in the CellLayout.  We need to add the new item with addInScreenFromBind()
+                    // to ensure that hotseat items are placed correctly.
                     mLauncher.getWorkspace().addInScreenFromBind(child, mInfo.container, mInfo.screenId,
                             mInfo.cellX, mInfo.cellY, mInfo.spanX, mInfo.spanY);
+
+                    // Focus the newly created child
+                    child.requestFocus();
                 }
             }
         };
diff --git a/src/com/android/launcher3/FolderIcon.java b/src/com/android/launcher3/FolderIcon.java
index de0df0d..bd61a6d 100644
--- a/src/com/android/launcher3/FolderIcon.java
+++ b/src/com/android/launcher3/FolderIcon.java
@@ -43,6 +43,8 @@
 
 import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.FolderInfo.FolderListener;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.util.Thunk;
 
 import java.util.ArrayList;
@@ -129,7 +131,7 @@
 
     private void init() {
         mLongPressHelper = new CheckLongPressHelper(this);
-        mStylusEventHelper = new StylusEventHelper(this);
+        mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
         setAccessibilityDelegate(LauncherAppState.getInstance().getAccessibilityDelegate());
     }
 
@@ -171,8 +173,7 @@
         icon.setOnClickListener(launcher);
         icon.mInfo = folderInfo;
         icon.mLauncher = launcher;
-        icon.setContentDescription(String.format(launcher.getString(R.string.folder_name_format),
-                folderInfo.title));
+        icon.setContentDescription(launcher.getString(R.string.folder_name_format, folderInfo.title));
         Folder folder = Folder.fromXml(launcher);
         folder.setDragController(launcher.getDragController());
         folder.setFolderIcon(icon);
@@ -712,8 +713,7 @@
 
     public void onTitleChanged(CharSequence title) {
         mFolderName.setText(title);
-        setContentDescription(String.format(getContext().getString(R.string.folder_name_format),
-                title));
+        setContentDescription(getContext().getString(R.string.folder_name_format, title));
     }
 
     @Override
@@ -723,7 +723,7 @@
         boolean result = super.onTouchEvent(event);
 
         // Check for a stylus button press, if it occurs cancel any long press checks.
-        if (mStylusEventHelper.checkAndPerformStylusEvent(event)) {
+        if (mStylusEventHelper.onMotionEvent(event)) {
             mLongPressHelper.cancelLongPress();
             return true;
         }
diff --git a/src/com/android/launcher3/FolderPagedView.java b/src/com/android/launcher3/FolderPagedView.java
index cc9c573..7aff832 100644
--- a/src/com/android/launcher3/FolderPagedView.java
+++ b/src/com/android/launcher3/FolderPagedView.java
@@ -30,6 +30,7 @@
 import com.android.launcher3.FocusHelper.PagedFolderKeyEventListener;
 import com.android.launcher3.PageIndicator.PageMarkerResources;
 import com.android.launcher3.Workspace.ItemOperator;
+import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.util.Thunk;
 
 import java.util.ArrayList;
@@ -435,8 +436,7 @@
     }
 
     public String getAccessibilityDescription() {
-        return String.format(getContext().getString(R.string.folder_opened),
-                mGridCountX, mGridCountY);
+        return getContext().getString(R.string.folder_opened, mGridCountX, mGridCountY);
     }
 
     /**
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index ea1c0fd..bf8ee9d 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -106,7 +106,6 @@
     private final BitmapFactory.Options mLowResOptions;
 
     private String mSystemState;
-    private Bitmap mLowResBitmap;
     private Canvas mLowResCanvas;
     private Paint mLowResPaint;
 
@@ -117,6 +116,8 @@
         mLauncherApps = LauncherAppsCompat.getInstance(mContext);
         mIconDpi = inv.fillResIconDpi;
         mIconDb = new IconDB(context);
+        mLowResCanvas = new Canvas();
+        mLowResPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
 
         mWorkerHandler = new Handler(LauncherModel.getWorkerLooper());
 
@@ -239,7 +240,7 @@
         long userSerial = mUserManager.getSerialNumberForUser(user);
         mIconDb.getWritableDatabase().delete(IconDB.TABLE_NAME,
                 IconDB.COLUMN_COMPONENT + " LIKE ? AND " + IconDB.COLUMN_USER + " = ?",
-                new String[] {packageName + "/%", Long.toString(userSerial)});
+                new String[]{packageName + "/%", Long.toString(userSerial)});
     }
 
     public void updateDbIcons(Set<String> ignorePackagesForMainUser) {
@@ -386,7 +387,8 @@
         entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, app.getUser());
         mCache.put(new ComponentKey(app.getComponentName(), app.getUser()), entry);
 
-        return newContentValues(entry.icon, entry.title.toString(), mActivityBgColor);
+        Bitmap lowResIcon = generateLowResIcon(entry.icon, mActivityBgColor);
+        return newContentValues(entry.icon, lowResIcon, entry.title.toString());
     }
 
     /**
@@ -616,22 +618,29 @@
             // Check the DB first.
             if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) {
                 try {
-                    PackageInfo info = mPackageManager.getPackageInfo(packageName, 0);
+                    int flags = UserHandleCompat.myUserHandle().equals(user) ? 0 :
+                        PackageManager.GET_UNINSTALLED_PACKAGES;
+                    PackageInfo info = mPackageManager.getPackageInfo(packageName, flags);
                     ApplicationInfo appInfo = info.applicationInfo;
                     if (appInfo == null) {
                         throw new NameNotFoundException("ApplicationInfo is null");
                     }
+
+                    // Load the full res icon for the application, but if useLowResIcon is set, then
+                    // only keep the low resolution icon instead of the larger full-sized icon
                     Drawable drawable = mUserManager.getBadgedDrawableForUser(
                             appInfo.loadIcon(mPackageManager), user);
-                    entry.icon = Utilities.createIconBitmap(drawable, mContext);
+                    Bitmap icon = Utilities.createIconBitmap(drawable, mContext);
+                    Bitmap lowResIcon =  generateLowResIcon(icon, mPackageBgColor);
                     entry.title = appInfo.loadLabel(mPackageManager);
                     entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
-                    entry.isLowResIcon = false;
+                    entry.icon = useLowResIcon ? lowResIcon : icon;
+                    entry.isLowResIcon = useLowResIcon;
 
                     // Add the icon in the DB here, since these do not get written during
                     // package updates.
                     ContentValues values =
-                            newContentValues(entry.icon, entry.title.toString(), mPackageBgColor);
+                            newContentValues(icon, lowResIcon, entry.title.toString());
                     addIconToDB(values, cacheKey.componentName, info,
                             mUserManager.getSerialNumberForUser(user));
 
@@ -671,9 +680,9 @@
             // pass
         }
 
-        ContentValues values = newContentValues(
-                Bitmap.createScaledBitmap(icon, idp.iconBitmapSize, idp.iconBitmapSize, true),
-                label, Color.TRANSPARENT);
+        icon = Bitmap.createScaledBitmap(icon, idp.iconBitmapSize, idp.iconBitmapSize, true);
+        Bitmap lowResIcon = generateLowResIcon(icon, Color.TRANSPARENT);
+        ContentValues values = newContentValues(icon, lowResIcon, label);
         values.put(IconDB.COLUMN_COMPONENT, componentName.flattenToString());
         values.put(IconDB.COLUMN_USER, userSerial);
         mIconDb.getWritableDatabase().insertWithOnConflict(IconDB.TABLE_NAME, null, values,
@@ -787,7 +796,7 @@
     }
 
     private static final class IconDB extends SQLiteOpenHelper {
-        private final static int DB_VERSION = 6;
+        private final static int DB_VERSION = 7;
 
         private final static String TABLE_NAME = "icons";
         private final static String COLUMN_ROWID = "rowid";
@@ -839,34 +848,37 @@
         }
     }
 
-    private ContentValues newContentValues(Bitmap icon, String label, int lowResBackgroundColor) {
+    private ContentValues newContentValues(Bitmap icon, Bitmap lowResIcon, String label) {
         ContentValues values = new ContentValues();
         values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(icon));
+        values.put(IconDB.COLUMN_ICON_LOW_RES, Utilities.flattenBitmap(lowResIcon));
 
         values.put(IconDB.COLUMN_LABEL, label);
         values.put(IconDB.COLUMN_SYSTEM_STATE, mSystemState);
 
+        return values;
+    }
+
+    /**
+     * Generates a new low-res icon given a high-res icon.
+     */
+    private Bitmap generateLowResIcon(Bitmap icon, int lowResBackgroundColor) {
         if (lowResBackgroundColor == Color.TRANSPARENT) {
-          values.put(IconDB.COLUMN_ICON_LOW_RES, Utilities.flattenBitmap(
-          Bitmap.createScaledBitmap(icon,
-                  icon.getWidth() / LOW_RES_SCALE_FACTOR,
-                  icon.getHeight() / LOW_RES_SCALE_FACTOR, true)));
+            return Bitmap.createScaledBitmap(icon,
+                            icon.getWidth() / LOW_RES_SCALE_FACTOR,
+                            icon.getHeight() / LOW_RES_SCALE_FACTOR, true);
         } else {
+            Bitmap lowResIcon = Bitmap.createBitmap(icon.getWidth() / LOW_RES_SCALE_FACTOR,
+                    icon.getHeight() / LOW_RES_SCALE_FACTOR, Bitmap.Config.RGB_565);
             synchronized (this) {
-                if (mLowResBitmap == null) {
-                    mLowResBitmap = Bitmap.createBitmap(icon.getWidth() / LOW_RES_SCALE_FACTOR,
-                            icon.getHeight() / LOW_RES_SCALE_FACTOR, Bitmap.Config.RGB_565);
-                    mLowResCanvas = new Canvas(mLowResBitmap);
-                    mLowResPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
-                }
                 mLowResCanvas.drawColor(lowResBackgroundColor);
                 mLowResCanvas.drawBitmap(icon, new Rect(0, 0, icon.getWidth(), icon.getHeight()),
-                        new Rect(0, 0, mLowResBitmap.getWidth(), mLowResBitmap.getHeight()),
+                        new Rect(0, 0, lowResIcon.getWidth(), lowResIcon.getHeight()),
                         mLowResPaint);
-                values.put(IconDB.COLUMN_ICON_LOW_RES, Utilities.flattenBitmap(mLowResBitmap));
+                mLowResCanvas.setBitmap(null);
             }
+            return lowResIcon;
         }
-        return values;
     }
 
     private static Bitmap loadIconNoResize(Cursor c, int iconIndex, BitmapFactory.Options options) {
diff --git a/src/com/android/launcher3/InfoDropTarget.java b/src/com/android/launcher3/InfoDropTarget.java
index b4a1445..d444640 100644
--- a/src/com/android/launcher3/InfoDropTarget.java
+++ b/src/com/android/launcher3/InfoDropTarget.java
@@ -20,7 +20,7 @@
 import android.content.Context;
 import android.util.AttributeSet;
 
-public class InfoDropTarget extends ButtonDropTarget {
+public class InfoDropTarget extends UninstallDropTarget {
 
     public InfoDropTarget(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
@@ -39,7 +39,10 @@
         setDrawable(R.drawable.ic_info_launcher);
     }
 
-    public static void startDetailsActivityForInfo(ItemInfo info, Launcher launcher) {
+    /**
+     * @return Whether the activity was started.
+     */
+    public static boolean startDetailsActivityForInfo(ItemInfo info, Launcher launcher) {
         ComponentName componentName = null;
         if (info instanceof AppInfo) {
             componentName = ((AppInfo) info).componentName;
@@ -47,10 +50,19 @@
             componentName = ((ShortcutInfo) info).intent.getComponent();
         } else if (info instanceof PendingAddItemInfo) {
             componentName = ((PendingAddItemInfo) info).componentName;
+        } else if (info instanceof LauncherAppWidgetInfo) {
+            componentName = ((LauncherAppWidgetInfo) info).providerName;
         }
         if (componentName != null) {
             launcher.startApplicationDetailsActivity(componentName, info.user);
+            return true;
         }
+        return false;
+    }
+
+    @Override
+    protected boolean startActivityWithUninstallAffordance(DragObject d) {
+        return startDetailsActivityForInfo(d.dragInfo, mLauncher);
     }
 
     @Override
@@ -59,11 +71,7 @@
     }
 
     public static boolean supportsDrop(Context context, ItemInfo info) {
-        return info instanceof AppInfo || info instanceof PendingAddItemInfo;
-    }
-
-    @Override
-    void completeDrop(DragObject d) {
-        startDetailsActivityForInfo(d.dragInfo, mLauncher);
+        return info instanceof AppInfo || info instanceof ShortcutInfo
+                || info instanceof PendingAddItemInfo || info instanceof LauncherAppWidgetInfo;
     }
 }
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index 956bb8c..aa5a18d 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -137,15 +137,8 @@
         return null;
     }
 
-    /**
-     * Write the fields of this item to the DB
-     * 
-     * @param context A context object to use for getting UserManagerCompat
-     * @param values
-     */
-
-    void onAddToDatabase(Context context, ContentValues values) {
-        values.put(LauncherSettings.BaseLauncherColumns.ITEM_TYPE, itemType);
+    public void writeToValues(ContentValues values) {
+        values.put(LauncherSettings.Favorites.ITEM_TYPE, itemType);
         values.put(LauncherSettings.Favorites.CONTAINER, container);
         values.put(LauncherSettings.Favorites.SCREEN, screenId);
         values.put(LauncherSettings.Favorites.CELLX, cellX);
@@ -153,6 +146,27 @@
         values.put(LauncherSettings.Favorites.SPANX, spanX);
         values.put(LauncherSettings.Favorites.SPANY, spanY);
         values.put(LauncherSettings.Favorites.RANK, rank);
+    }
+
+    public void readFromValues(ContentValues values) {
+        itemType = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE);
+        container = values.getAsLong(LauncherSettings.Favorites.CONTAINER);
+        screenId = values.getAsLong(LauncherSettings.Favorites.SCREEN);
+        cellX = values.getAsInteger(LauncherSettings.Favorites.CELLX);
+        cellY = values.getAsInteger(LauncherSettings.Favorites.CELLY);
+        spanX = values.getAsInteger(LauncherSettings.Favorites.SPANX);
+        spanY = values.getAsInteger(LauncherSettings.Favorites.SPANY);
+        rank = values.getAsInteger(LauncherSettings.Favorites.RANK);
+    }
+
+    /**
+     * Write the fields of this item to the DB
+     *
+     * @param context A context object to use for getting UserManagerCompat
+     * @param values
+     */
+    void onAddToDatabase(Context context, ContentValues values) {
+        writeToValues(values);
         long serialNumber = UserManagerCompat.getInstance(context).getSerialNumberForUser(user);
         values.put(LauncherSettings.Favorites.PROFILE_ID, serialNumber);
 
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 5f578f7..8393910 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -35,6 +35,7 @@
 import android.content.BroadcastReceiver;
 import android.content.ComponentCallbacks2;
 import android.content.ComponentName;
+import android.content.ContentValues;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
@@ -72,7 +73,6 @@
 import android.view.Display;
 import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
-import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MotionEvent;
 import android.view.Surface;
@@ -101,11 +101,15 @@
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.LongArrayMap;
 import com.android.launcher3.util.TestingUtils;
 import com.android.launcher3.util.Thunk;
+import com.android.launcher3.util.ViewOnDrawExecutor;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
 import com.android.launcher3.widget.WidgetHostViewLoader;
 import com.android.launcher3.widget.WidgetsContainerView;
@@ -122,7 +126,6 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Default launcher application.
@@ -130,7 +133,7 @@
 public class Launcher extends Activity
         implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
                    View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener {
-    static final String TAG = "Launcher";
+    public static final String TAG = "Launcher";
     static final boolean LOGD = false;
 
     static final boolean PROFILE_STARTUP = false;
@@ -173,24 +176,12 @@
     private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
     // Type: int
     private static final String RUNTIME_STATE = "launcher.state";
-    // Type: int
-    private static final String RUNTIME_STATE_PENDING_ADD_CONTAINER = "launcher.add_container";
-    // Type: int
-    private static final String RUNTIME_STATE_PENDING_ADD_SCREEN = "launcher.add_screen";
-    // Type: int
-    private static final String RUNTIME_STATE_PENDING_ADD_CELL_X = "launcher.add_cell_x";
-    // Type: int
-    private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cell_y";
-    // Type: int
-    private static final String RUNTIME_STATE_PENDING_ADD_SPAN_X = "launcher.add_span_x";
-    // Type: int
-    private static final String RUNTIME_STATE_PENDING_ADD_SPAN_Y = "launcher.add_span_y";
+    // Type: Content Values / parcelable
+    private static final String RUNTIME_STATE_PENDING_ADD_ITEM = "launcher.add_item";
     // Type: parcelable
     private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_INFO = "launcher.add_widget_info";
     // Type: parcelable
     private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_ID = "launcher.add_widget_id";
-    // Type: int[]
-    private static final String RUNTIME_STATE_VIEW_IDS = "launcher.view_ids";
 
     static final String INTRO_SCREEN_DISMISSED = "launcher.intro_screen_dismissed";
     static final String FIRST_RUN_ACTIVITY_DISPLAYED = "launcher.first_run_activity_displayed";
@@ -205,7 +196,8 @@
     public static final String USER_HAS_MIGRATED = "launcher.user_migrated_from_old_data";
 
     /** The different states that Launcher can be in. */
-    enum State { NONE, WORKSPACE, APPS, APPS_SPRING_LOADED, WIDGETS, WIDGETS_SPRING_LOADED }
+    enum State { NONE, WORKSPACE, WORKSPACE_SPRING_LOADED, APPS, APPS_SPRING_LOADED,
+        WIDGETS, WIDGETS_SPRING_LOADED }
 
     @Thunk State mState = State.WORKSPACE;
     @Thunk LauncherStateTransitionAnimation mStateTransitionAnimation;
@@ -221,9 +213,6 @@
     private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
     private static final int ACTIVITY_START_DELAY = 1000;
 
-    private HashMap<Integer, Integer> mItemIdToViewId = new HashMap<Integer, Integer>();
-    private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
-
     // How long to wait before the new-shortcut animation automatically pans the workspace
     private static int NEW_APPS_PAGE_MOVE_DELAY = 500;
     private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
@@ -232,8 +221,6 @@
     private final BroadcastReceiver mCloseSystemDialogsReceiver
             = new CloseSystemDialogsIntentReceiver();
 
-    private LayoutInflater mInflater;
-
     @Thunk Workspace mWorkspace;
     private View mLauncherView;
     private View mPageIndicators;
@@ -258,6 +245,7 @@
     private View mWidgetsButton;
 
     private SearchDropTargetBar mSearchDropTargetBar;
+    private AppInfoDropTargetBar mAppInfoDropTargetBar;
 
     // Main container view for the all apps screen.
     @Thunk AllAppsContainerView mAppsView;
@@ -266,7 +254,6 @@
     @Thunk WidgetsContainerView mWidgetsView;
     @Thunk WidgetsModel mWidgetsModel;
 
-    private boolean mAutoAdvanceRunning = false;
     private AppWidgetHostView mQsb;
 
     private Bundle mSavedState;
@@ -286,6 +273,7 @@
 
     private ArrayList<Runnable> mBindOnResumeCallbacks = new ArrayList<Runnable>();
     private ArrayList<Runnable> mOnResumeCallbacks = new ArrayList<Runnable>();
+    private ViewOnDrawExecutor mPendingExecutor;
 
     private LauncherModel mModel;
     private IconCache mIconCache;
@@ -294,22 +282,25 @@
     private boolean mHasFocus = false;
     private boolean mAttached = false;
 
+    private LauncherClings mClings;
+
     private static LongArrayMap<FolderInfo> sFolders = new LongArrayMap<>();
 
     private View.OnTouchListener mHapticFeedbackTouchListener;
 
     // Related to the auto-advancing of widgets
     private final int ADVANCE_MSG = 1;
-    private final int mAdvanceInterval = 20000;
-    private final int mAdvanceStagger = 250;
+    private static final int ADVANCE_INTERVAL = 20000;
+    private static final int ADVANCE_STAGGER = 250;
+
+    private boolean mAutoAdvanceRunning = false;
     private long mAutoAdvanceSentTime;
     private long mAutoAdvanceTimeLeft = -1;
-    @Thunk HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance =
-        new HashMap<View, AppWidgetProviderInfo>();
+    @Thunk HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance = new HashMap<>();
 
     // Determines how long to wait after a rotation before restoring the screen orientation to
     // match the sensor state.
-    private final int mRestoreScreenOrientationDelay = 500;
+    private static final int RESTORE_SCREEN_ORIENTATION_DELAY = 500;
 
     @Thunk Drawable mWorkspaceBackgroundDrawable;
 
@@ -336,6 +327,8 @@
 
     private DeviceProfile mDeviceProfile;
 
+    private boolean mMoveToDefaultScreenFromNewIntent;
+
     // This is set to the view that launched the activity that navigated the user away from
     // launcher. Since there is no callback for when the activity has finished launching, enable
     // the press state and keep this reference to reset the press state when we return to launcher.
@@ -428,7 +421,6 @@
         mIconCache = app.getIconCache();
 
         mDragController = new DragController(this);
-        mInflater = getLayoutInflater();
         mStateTransitionAnimation = new LauncherStateTransitionAnimation(this);
 
         mStats = new Stats(this);
@@ -485,7 +477,7 @@
         // In case we are on a device with locked rotation, we should look at preferences to check
         // if the user has specifically allowed rotation.
         if (!mRotationEnabled) {
-            mRotationEnabled = Utilities.isAllowRotationPrefEnabled(getApplicationContext(), false);
+            mRotationEnabled = Utilities.isAllowRotationPrefEnabled(getApplicationContext());
         }
 
         // On large interfaces, or on devices that a user has specifically enabled screen rotation,
@@ -633,44 +625,18 @@
         return mStats;
     }
 
-    public LayoutInflater getInflater() {
-        return mInflater;
-    }
-
     public boolean isDraggingEnabled() {
         // We prevent dragging when we are loading the workspace as it is possible to pick up a view
         // that is subsequently removed from the workspace in startBinding().
-        return !mModel.isLoadingWorkspace();
-    }
-
-    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
-    public static int generateViewId() {
-        if (Utilities.ATLEAST_JB_MR1) {
-            return View.generateViewId();
-        } else {
-            // View.generateViewId() is not available. The following fallback logic is a copy
-            // of its implementation.
-            for (;;) {
-                final int result = sNextGeneratedId.get();
-                // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
-                int newValue = result + 1;
-                if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
-                if (sNextGeneratedId.compareAndSet(result, newValue)) {
-                    return result;
-                }
-            }
-        }
+        return !isWorkspaceLoading();
     }
 
     public int getViewIdForItem(ItemInfo info) {
-        // This cast is safe given the > 2B range for int.
-        int itemId = (int) info.id;
-        if (mItemIdToViewId.containsKey(itemId)) {
-            return mItemIdToViewId.get(itemId);
-        }
-        int viewId = generateViewId();
-        mItemIdToViewId.put(itemId, viewId);
-        return viewId;
+        // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
+        // This cast is safe as long as the id < 0x00FFFFFF
+        // Since we jail all the dynamically generated views, there should be no clashes
+        // with any other views.
+        return (int) info.id;
     }
 
     /**
@@ -738,6 +704,9 @@
             return;
         } else if (requestCode == REQUEST_PICK_WALLPAPER) {
             if (resultCode == RESULT_OK && mWorkspace.isInOverviewMode()) {
+                // User could have free-scrolled between pages before picking a wallpaper; make sure
+                // we move to the closest one now.
+                mWorkspace.setCurrentPage(mWorkspace.getPageNearestToCenterOfScreen());
                 showWorkspace(false);
             }
             return;
@@ -1030,14 +999,21 @@
             Log.d(TAG, "Time spent in onResume: " + (System.currentTimeMillis() - startTime));
         }
 
-        if (mWorkspace.getCustomContentCallbacks() != null) {
+        // We want to suppress callbacks about CustomContent being shown if we have just received
+        // onNewIntent while the user was present within launcher. In that case, we post a call
+        // to move the user to the main screen (which will occur after onResume). We don't want to
+        // have onHide (from onPause), then onShow, then onHide again, which we get if we don't
+        // suppress here.
+        if (mWorkspace.getCustomContentCallbacks() != null
+                && !mMoveToDefaultScreenFromNewIntent) {
             // If we are resuming and the custom content is the current page, we call onShow().
-            // It is also poassible that onShow will instead be called slightly after first layout
+            // It is also possible that onShow will instead be called slightly after first layout
             // if PagedView#setRestorePage was set to the custom content page in onCreate().
             if (mWorkspace.isOnOrMovingToCustomContent()) {
                 mWorkspace.getCustomContentCallbacks().onShow(true);
             }
         }
+        mMoveToDefaultScreenFromNewIntent = false;
         updateInteraction(Workspace.State.NORMAL, mWorkspace.getState());
         mWorkspace.onResume();
 
@@ -1283,7 +1259,6 @@
      *
      * @param savedState The previous state.
      */
-    @SuppressWarnings("unchecked")
     private void restoreState(Bundle savedState) {
         if (savedState == null) {
             return;
@@ -1300,16 +1275,9 @@
             mWorkspace.setRestorePage(currentScreen);
         }
 
-        final long pendingAddContainer = savedState.getLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, -1);
-        final long pendingAddScreen = savedState.getLong(RUNTIME_STATE_PENDING_ADD_SCREEN, -1);
-
-        if (pendingAddContainer != ItemInfo.NO_ID && pendingAddScreen > -1) {
-            mPendingAddInfo.container = pendingAddContainer;
-            mPendingAddInfo.screenId = pendingAddScreen;
-            mPendingAddInfo.cellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X);
-            mPendingAddInfo.cellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y);
-            mPendingAddInfo.spanX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_X);
-            mPendingAddInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y);
+        ContentValues itemValues = savedState.getParcelable(RUNTIME_STATE_PENDING_ADD_ITEM);
+        if (itemValues != null) {
+            mPendingAddInfo.readFromValues(itemValues);
             AppWidgetProviderInfo info = savedState.getParcelable(
                     RUNTIME_STATE_PENDING_ADD_WIDGET_INFO);
             mPendingAddWidgetInfo = info == null ?
@@ -1319,9 +1287,6 @@
             setWaitingForResult(true);
             mRestoring = true;
         }
-
-        mItemIdToViewId = (HashMap<Integer, Integer>)
-                savedState.getSerializable(RUNTIME_STATE_VIEW_IDS);
     }
 
     /**
@@ -1351,38 +1316,48 @@
         }
 
         mOverviewPanel = (ViewGroup) findViewById(R.id.overview_panel);
+        // Long-clicking buttons in the overview panel does the same thing as clicking them.
+        OnLongClickListener performClickOnLongClick = new OnLongClickListener() {
+            @Override
+            public boolean onLongClick(View v) {
+                return v.performClick();
+            }
+        };
         mWidgetsButton = findViewById(R.id.widget_button);
         mWidgetsButton.setOnClickListener(new OnClickListener() {
             @Override
-            public void onClick(View arg0) {
+            public void onClick(View view) {
                 if (!mWorkspace.isSwitchingState()) {
-                    onClickAddWidgetButton(arg0);
+                    onClickAddWidgetButton(view);
                 }
             }
         });
+        mWidgetsButton.setOnLongClickListener(performClickOnLongClick);
         mWidgetsButton.setOnTouchListener(getHapticFeedbackTouchListener());
 
         View wallpaperButton = findViewById(R.id.wallpaper_button);
         wallpaperButton.setOnClickListener(new OnClickListener() {
             @Override
-            public void onClick(View arg0) {
+            public void onClick(View view) {
                 if (!mWorkspace.isSwitchingState()) {
-                    onClickWallpaperPicker(arg0);
+                    onClickWallpaperPicker(view);
                 }
             }
         });
+        wallpaperButton.setOnLongClickListener(performClickOnLongClick);
         wallpaperButton.setOnTouchListener(getHapticFeedbackTouchListener());
 
         View settingsButton = findViewById(R.id.settings_button);
         if (hasSettings()) {
             settingsButton.setOnClickListener(new OnClickListener() {
                 @Override
-                public void onClick(View arg0) {
+                public void onClick(View view) {
                     if (!mWorkspace.isSwitchingState()) {
-                        onClickSettingsButton(arg0);
+                        onClickSettingsButton(view);
                     }
                 }
             });
+            settingsButton.setOnLongClickListener(performClickOnLongClick);
             settingsButton.setOnTouchListener(getHapticFeedbackTouchListener());
         } else {
             settingsButton.setVisibility(View.GONE);
@@ -1396,9 +1371,12 @@
         mWorkspace.setup(dragController);
         dragController.addDragListener(mWorkspace);
 
-        // Get the search/delete bar
+        // Get the search/delete/uninstall bar
         mSearchDropTargetBar = (SearchDropTargetBar)
                 mDragLayer.findViewById(R.id.search_drop_target_bar);
+        // Get the app info bar
+        mAppInfoDropTargetBar = (AppInfoDropTargetBar)
+                mDragLayer.findViewById(R.id.app_info_drop_target_bar);
 
         // Setup Apps and Widgets
         mAppsView = (AllAppsContainerView) findViewById(R.id.apps_view);
@@ -1411,13 +1389,15 @@
 
         // Setup the drag controller (drop targets have to be added in reverse order in priority)
         dragController.setDragScoller(mWorkspace);
-        dragController.setScrollView(mDragLayer);
         dragController.setMoveTarget(mWorkspace);
         dragController.addDropTarget(mWorkspace);
         if (mSearchDropTargetBar != null) {
             mSearchDropTargetBar.setup(this, dragController);
             mSearchDropTargetBar.setQsbSearchBar(getOrCreateQsbBar());
         }
+        if (mAppInfoDropTargetBar != null) {
+            mAppInfoDropTargetBar.setup(this, dragController);
+        }
 
         if (TestingUtils.MEMORY_DUMP_ENABLED) {
             TestingUtils.addWeightWatcher(this);
@@ -1457,7 +1437,7 @@
      * @return A View inflated from layoutResId.
      */
     public View createShortcut(ViewGroup parent, ShortcutInfo info) {
-        BubbleTextView favorite = (BubbleTextView) mInflater.inflate(R.layout.app_icon,
+        BubbleTextView favorite = (BubbleTextView) getLayoutInflater().inflate(R.layout.app_icon,
                 parent, false);
         favorite.applyFromShortcutInfo(info, mIconCache);
         favorite.setCompoundDrawablePadding(mDeviceProfile.iconDrawablePaddingPx);
@@ -1580,7 +1560,10 @@
                 // processing a multi-step drop
                 if (mAppsView != null && mWidgetsView != null &&
                         mPendingAddInfo.container == ItemInfo.NO_ID) {
-                    showWorkspace(false);
+                    if (!showWorkspace(false)) {
+                        // If we are already on the workspace, then manually reset all apps
+                        mAppsView.reset();
+                    }
                 }
             } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
                 mUserPresent = true;
@@ -1703,11 +1686,11 @@
         if (autoAdvanceRunning != mAutoAdvanceRunning) {
             mAutoAdvanceRunning = autoAdvanceRunning;
             if (autoAdvanceRunning) {
-                long delay = mAutoAdvanceTimeLeft == -1 ? mAdvanceInterval : mAutoAdvanceTimeLeft;
+                long delay = mAutoAdvanceTimeLeft == -1 ? ADVANCE_INTERVAL : mAutoAdvanceTimeLeft;
                 sendAdvanceMessage(delay);
             } else {
                 if (!mWidgetsToAdvance.isEmpty()) {
-                    mAutoAdvanceTimeLeft = Math.max(0, mAdvanceInterval -
+                    mAutoAdvanceTimeLeft = Math.max(0, ADVANCE_INTERVAL -
                             (System.currentTimeMillis() - mAutoAdvanceSentTime));
                 }
                 mHandler.removeMessages(ADVANCE_MSG);
@@ -1724,7 +1707,7 @@
                 int i = 0;
                 for (View key: mWidgetsToAdvance.keySet()) {
                     final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId);
-                    final int delay = mAdvanceStagger * i;
+                    final int delay = ADVANCE_STAGGER * i;
                     if (v instanceof Advanceable) {
                         mHandler.postDelayed(new Runnable() {
                            public void run() {
@@ -1734,13 +1717,13 @@
                     }
                     i++;
                 }
-                sendAdvanceMessage(mAdvanceInterval);
+                sendAdvanceMessage(ADVANCE_INTERVAL);
             }
             return true;
         }
     });
 
-    void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) {
+    private void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) {
         if (appWidgetInfo == null || appWidgetInfo.autoAdvanceViewId == -1) return;
         View v = hostView.findViewById(appWidgetInfo.autoAdvanceViewId);
         if (v instanceof Advanceable) {
@@ -1750,18 +1733,13 @@
         }
     }
 
-    void removeWidgetToAutoAdvance(View hostView) {
+    private void removeWidgetToAutoAdvance(View hostView) {
         if (mWidgetsToAdvance.containsKey(hostView)) {
             mWidgetsToAdvance.remove(hostView);
             updateAutoAdvanceState();
         }
     }
 
-    public void removeAppWidget(LauncherAppWidgetInfo launcherInfo) {
-        removeWidgetToAutoAdvance(launcherInfo.hostView);
-        launcherInfo.hostView = null;
-    }
-
     public void showOutOfSpaceMessage(boolean isHotseatLayout) {
         int strId = (isHotseatLayout ? R.string.hotseat_out_of_space : R.string.out_of_space);
         Toast.makeText(this, getString(strId), Toast.LENGTH_SHORT).show();
@@ -1795,6 +1773,10 @@
         return mSearchDropTargetBar;
     }
 
+    public AppInfoDropTargetBar getAppInfoDropTargetBar() {
+        return mAppInfoDropTargetBar;
+    }
+
     public LauncherAppWidgetHost getAppWidgetHost() {
         return mAppWidgetHost;
     }
@@ -1888,6 +1870,10 @@
                     mLauncherCallbacks.shouldMoveToDefaultScreenOnHomeIntent() : true;
             if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() &&
                     openFolder == null && moveToDefaultScreen) {
+
+                // We use this flag to suppress noisy callbacks above custom content state
+                // from onResume.
+                mMoveToDefaultScreenFromNewIntent = true;
                 mWorkspace.post(new Runnable() {
                     @Override
                     public void run() {
@@ -1925,19 +1911,15 @@
 
         if (mPendingAddInfo.container != ItemInfo.NO_ID && mPendingAddInfo.screenId > -1 &&
                 mWaitingForResult) {
-            outState.putLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, mPendingAddInfo.container);
-            outState.putLong(RUNTIME_STATE_PENDING_ADD_SCREEN, mPendingAddInfo.screenId);
-            outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, mPendingAddInfo.cellX);
-            outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, mPendingAddInfo.cellY);
-            outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_X, mPendingAddInfo.spanX);
-            outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y, mPendingAddInfo.spanY);
+            ContentValues itemValues = new ContentValues();
+            mPendingAddInfo.writeToValues(itemValues);
+            outState.putParcelable(RUNTIME_STATE_PENDING_ADD_ITEM, itemValues);
             outState.putParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO, mPendingAddWidgetInfo);
             outState.putInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID, mPendingAddWidgetId);
         }
 
         // Save the current widgets tray?
         // TODO(hyunyoungs)
-        outState.putSerializable(RUNTIME_STATE_VIEW_IDS, mItemIdToViewId);
 
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onSaveInstanceState(outState);
@@ -2344,10 +2326,72 @@
         return newFolder;
     }
 
-    void removeFolder(FolderInfo folder) {
+    /**
+     * Unbinds the view for the specified item, and removes the item and all its children.
+     *
+     * @param v the view being removed.
+     * @param containerInfo the {@link FolderInfo} container of this view (can be null).
+     * @param itemInfo the {@link ItemInfo} for this view.
+     * @param deleteFromDb whether or not to delete this item from the db.
+     */
+    public boolean removeItem(View v, FolderInfo containerInfo, ItemInfo itemInfo,
+            boolean deleteFromDb) {
+        if (itemInfo instanceof ShortcutInfo) {
+            // Remove the shortcut from the folder before removing it from launcher
+            if (containerInfo != null) {
+                containerInfo.remove((ShortcutInfo) itemInfo);
+            } else {
+                mWorkspace.removeWorkspaceItem(v);
+            }
+            if (deleteFromDb) {
+                LauncherModel.deleteItemFromDatabase(this, itemInfo);
+            }
+        } else if (itemInfo instanceof FolderInfo) {
+            final FolderInfo folderInfo = (FolderInfo) itemInfo;
+            unbindFolder(folderInfo);
+            mWorkspace.removeWorkspaceItem(v);
+            if (deleteFromDb) {
+                LauncherModel.deleteFolderAndContentsFromDatabase(this, folderInfo);
+            }
+        } else if (itemInfo instanceof LauncherAppWidgetInfo) {
+            final LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) itemInfo;
+            unbindAppWidget(widgetInfo, deleteFromDb);
+            if (deleteFromDb) {
+                LauncherModel.deleteItemFromDatabase(this, widgetInfo);
+            }
+        } else {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Unbinds any launcher references to the folder.
+     */
+    private void unbindFolder(FolderInfo folder) {
         sFolders.remove(folder.id);
     }
 
+    /**
+     * Unbinds any launcher references to the widget and deletes the app widget id.
+     */
+    private void unbindAppWidget(final LauncherAppWidgetInfo widgetInfo, boolean deleteAppWidgetId) {
+        final LauncherAppWidgetHost appWidgetHost = getAppWidgetHost();
+        if (deleteAppWidgetId && appWidgetHost != null &&
+                !widgetInfo.isCustomWidget() && widgetInfo.isWidgetIdValid()) {
+            // Deleting an app widget ID is a void call but writes to disk before returning
+            // to the caller...
+            new AsyncTask<Void, Void, Void>() {
+                public Void doInBackground(Void ... args) {
+                    appWidgetHost.deleteAppWidgetId(widgetInfo.appWidgetId);
+                    return null;
+                }
+            }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+        }
+        removeWidgetToAutoAdvance(widgetInfo.hostView);
+        widgetInfo.hostView = null;
+    }
+
     @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
         if (event.getAction() == KeyEvent.ACTION_DOWN) {
@@ -2694,7 +2738,10 @@
      */
     protected void onClickWallpaperPicker(View v) {
         if (LOGD) Log.d(TAG, "onClickWallpaperPicker");
-        startActivityForResult(new Intent(Intent.ACTION_SET_WALLPAPER).setPackage(getPackageName()),
+        int pageScroll = mWorkspace.getScrollForPage(mWorkspace.getPageNearestToCenterOfScreen());
+        float offset = mWorkspace.mWallpaperOffset.wallpaperOffsetForScroll(pageScroll);
+        startActivityForResult(new Intent(Intent.ACTION_SET_WALLPAPER).setPackage(getPackageName())
+                        .putExtra(WallpaperPickerActivity.EXTRA_WALLPAPER_OFFSET, offset),
                 REQUEST_PICK_WALLPAPER);
 
         if (mLauncherCallbacks != null) {
@@ -3200,11 +3247,11 @@
         }
     }
 
-    public void showWorkspace(boolean animated) {
-        showWorkspace(animated, null);
+    public boolean showWorkspace(boolean animated) {
+        return showWorkspace(animated, null);
     }
 
-    public void showWorkspace(boolean animated, Runnable onCompleteRunnable) {
+    public boolean showWorkspace(boolean animated, Runnable onCompleteRunnable) {
         boolean changed = mState != State.WORKSPACE ||
                 mWorkspace.getState() != Workspace.State.NORMAL;
         if (changed) {
@@ -3230,6 +3277,7 @@
             getWindow().getDecorView()
                     .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
         }
+        return changed;
     }
 
     void showOverviewMode(boolean animated) {
@@ -3334,20 +3382,26 @@
 
     public void enterSpringLoadedDragMode() {
         if (LOGD) Log.d(TAG, String.format("enterSpringLoadedDragMode [mState=%s", mState.name()));
-        if (mState == State.WORKSPACE || mState == State.APPS_SPRING_LOADED ||
-                mState == State.WIDGETS_SPRING_LOADED) {
+        if (isStateSpringLoaded()) {
             return;
         }
 
         mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
                 Workspace.State.SPRING_LOADED, true /* animated */,
                 null /* onCompleteRunnable */);
-        mState = isAppsViewVisible() ? State.APPS_SPRING_LOADED : State.WIDGETS_SPRING_LOADED;
+
+        if (isAppsViewVisible()) {
+            mState = State.APPS_SPRING_LOADED;
+        } else if (isWidgetsViewVisible()) {
+            mState = State.WIDGETS_SPRING_LOADED;
+        } else {
+            mState = State.WORKSPACE_SPRING_LOADED;
+        }
     }
 
     public void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, int delay,
             final Runnable onCompleteRunnable) {
-        if (mState != State.APPS_SPRING_LOADED && mState != State.WIDGETS_SPRING_LOADED) return;
+        if (!isStateSpringLoaded()) return;
 
         mHandler.postDelayed(new Runnable() {
             @Override
@@ -3367,12 +3421,19 @@
         }, delay);
     }
 
+    private boolean isStateSpringLoaded() {
+        return mState == State.WORKSPACE_SPRING_LOADED || mState == State.APPS_SPRING_LOADED
+                || mState == State.WIDGETS_SPRING_LOADED;
+    }
+
     void exitSpringLoadedDragMode() {
         if (mState == State.APPS_SPRING_LOADED) {
             showAppsView(true /* animated */, false /* resetListToTop */,
                     false /* updatePredictedApps */, false /* focusSearchBar */);
         } else if (mState == State.WIDGETS_SPRING_LOADED) {
             showWidgetsView(true, false);
+        } else if (mState == State.WORKSPACE_SPRING_LOADED) {
+            showWorkspace(true);
         }
     }
 
@@ -3448,6 +3509,7 @@
             mAppWidgetHost.setQsbWidgetId(widgetId);
             if (widgetId != -1) {
                 mQsb = mAppWidgetHost.createView(this, widgetId, searchProvider);
+                mQsb.setId(R.id.qsb_widget);
                 mQsb.updateAppWidgetOptions(opts);
                 mQsb.setPadding(0, 0, 0, 0);
                 mSearchDropTargetBar.addView(mQsb);
@@ -3565,6 +3627,19 @@
     }
 
     /**
+     * Clear any pending bind callbacks. This is called when is loader is planning to
+     * perform a full rebind from scratch.
+     */
+    @Override
+    public void clearPendingBinds() {
+        mBindOnResumeCallbacks.clear();
+        if (mPendingExecutor != null) {
+            mPendingExecutor.markCompleted();
+            mPendingExecutor = null;
+        }
+    }
+
+    /**
      * Refreshes the shortcuts shown on the workspace.
      *
      * Implementation of the method from LauncherModel.Callbacks.
@@ -3572,11 +3647,6 @@
     public void startBinding() {
         setWorkspaceLoading(true);
 
-        // If we're starting binding all over again, clear any bind calls we'd postponed in
-        // the past (see waitUntilResume) -- we don't need them since we're starting binding
-        // from scratch again
-        mBindOnResumeCallbacks.clear();
-
         // Clear the workspace because it's going to be rebound
         mWorkspace.clearDropTargets();
         mWorkspace.removeAllWorkspaceScreens();
@@ -3680,11 +3750,12 @@
                 continue;
             }
 
+            final View view;
             switch (item.itemType) {
                 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                     ShortcutInfo info = (ShortcutInfo) item;
-                    View shortcut = createShortcut(info);
+                    view = createShortcut(info);
 
                     /*
                      * TODO: FIX collision case
@@ -3703,28 +3774,26 @@
                             }
                         }
                     }
-
-                    workspace.addInScreenFromBind(shortcut, item.container, item.screenId, item.cellX,
-                            item.cellY, 1, 1);
-                    if (animateIcons) {
-                        // Animate all the applications up now
-                        shortcut.setAlpha(0f);
-                        shortcut.setScaleX(0f);
-                        shortcut.setScaleY(0f);
-                        bounceAnims.add(createNewAppBounceAnimation(shortcut, i));
-                        newShortcutsScreenId = item.screenId;
-                    }
                     break;
                 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
-                    FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
+                    view = FolderIcon.fromXml(R.layout.folder_icon, this,
                             (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
                             (FolderInfo) item, mIconCache);
-                    workspace.addInScreenFromBind(newFolder, item.container, item.screenId, item.cellX,
-                            item.cellY, 1, 1);
                     break;
                 default:
                     throw new RuntimeException("Invalid Item Type");
             }
+
+            workspace.addInScreenFromBind(view, item.container, item.screenId, item.cellX,
+                    item.cellY, 1, 1);
+            if (animateIcons) {
+                // Animate all the applications up now
+                view.setAlpha(0f);
+                view.setScaleX(0f);
+                view.setScaleY(0f);
+                bounceAnims.add(createNewAppBounceAnimation(view, i));
+                newShortcutsScreenId = item.screenId;
+            }
         }
 
         if (animateIcons) {
@@ -3918,6 +3987,21 @@
         mSynchronouslyBoundPages.add(page);
     }
 
+    @Override
+    public void executeOnNextDraw(ViewOnDrawExecutor executor) {
+        if (mPendingExecutor != null) {
+            mPendingExecutor.markCompleted();
+        }
+        mPendingExecutor = executor;
+        executor.attachTo(this);
+    }
+
+    public void clearPendingExecutor(ViewOnDrawExecutor executor) {
+        if (mPendingExecutor == executor) {
+            mPendingExecutor = null;
+        }
+    }
+
     /**
      * Callback saying that there aren't any more items to bind.
      *
@@ -3989,7 +4073,8 @@
 
     private boolean canRunNewAppsAnimation() {
         long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime();
-        return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
+        return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000)
+                && (mClings == null || !mClings.isVisible());
     }
 
     private ValueAnimator createNewAppBounceAnimation(View v, int i) {
@@ -4252,7 +4337,7 @@
                     public void run() {
                         setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
                     }
-                }, mRestoreScreenOrientationDelay);
+                }, RESTORE_SCREEN_ORIENTATION_DELAY);
             }
         }
     }
@@ -4413,6 +4498,7 @@
         // launcher2). Otherwise, we prompt the user upon started for migration
         LauncherClings launcherClings = new LauncherClings(this);
         if (launcherClings.shouldShowFirstRunOrMigrationClings()) {
+            mClings = launcherClings;
             if (mModel.canMigrateFromOldLauncherDb(this)) {
                 launcherClings.showMigrationCling();
             } else {
diff --git a/src/com/android/launcher3/LauncherAppWidgetHostView.java b/src/com/android/launcher3/LauncherAppWidgetHostView.java
index cf461a5..01332fb 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHostView.java
@@ -26,7 +26,8 @@
 import android.view.ViewGroup;
 import android.widget.RemoteViews;
 
-import com.android.launcher3.DragLayer.TouchCompleteListener;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.dragndrop.DragLayer.TouchCompleteListener;
 
 /**
  * {@inheritDoc}
@@ -47,7 +48,7 @@
         super(context);
         mContext = context;
         mLongPressHelper = new CheckLongPressHelper(this);
-        mStylusEventHelper = new StylusEventHelper(this);
+        mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
         mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
         mDragLayer = ((Launcher) context).getDragLayer();
         setAccessibilityDelegate(LauncherAppState.getInstance().getAccessibilityDelegate());
@@ -93,7 +94,7 @@
 
         // Watch for longpress or stylus button press events at this level to
         // make sure users can always pick up this widget
-        if (mStylusEventHelper.checkAndPerformStylusEvent(ev)) {
+        if (mStylusEventHelper.onMotionEvent(ev)) {
             mLongPressHelper.cancelLongPress();
             return true;
         }
diff --git a/src/com/android/launcher3/LauncherClings.java b/src/com/android/launcher3/LauncherClings.java
index 458844c..43d05a6 100644
--- a/src/com/android/launcher3/LauncherClings.java
+++ b/src/com/android/launcher3/LauncherClings.java
@@ -53,6 +53,7 @@
 
     @Thunk Launcher mLauncher;
     private LayoutInflater mInflater;
+    @Thunk boolean mIsVisible;
 
     /** Ctor */
     public LauncherClings(Launcher launcher) {
@@ -108,6 +109,7 @@
      */
     public void showMigrationCling() {
         mLauncher.onLauncherClingShown();
+        mIsVisible = true;
         mLauncher.hideWorkspaceSearchAndHotseat();
 
         ViewGroup root = (ViewGroup) mLauncher.findViewById(R.id.launcher);
@@ -134,6 +136,7 @@
     }
 
     public void showLongPressCling(boolean showWelcome) {
+        mIsVisible = true;
         ViewGroup root = (ViewGroup) mLauncher.findViewById(R.id.launcher);
         View cling = mInflater.inflate(R.layout.longpress_cling, root, false);
 
@@ -221,6 +224,7 @@
                     mLauncher.getSharedPrefs().edit()
                         .putBoolean(flag, true)
                         .apply();
+                    mIsVisible = false;
                     if (postAnimationCb != null) {
                         postAnimationCb.run();
                     }
@@ -234,6 +238,10 @@
         }
     }
 
+    public boolean isVisible() {
+        return mIsVisible;
+    }
+
     /** Returns whether the clings are enabled or should be shown */
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
     private boolean areClingsEnabled() {
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index b390f13..0f043ce 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -34,6 +34,7 @@
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.net.Uri;
+import android.os.DeadObjectException;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -63,6 +64,7 @@
 import com.android.launcher3.util.LongArrayMap;
 import com.android.launcher3.util.ManagedProfileHeuristic;
 import com.android.launcher3.util.Thunk;
+import com.android.launcher3.util.ViewOnDrawExecutor;
 
 import java.lang.ref.WeakReference;
 import java.net.URISyntaxException;
@@ -78,6 +80,7 @@
 import java.util.List;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.concurrent.Executor;
 
 /**
  * Maintains in-memory state of the Launcher. It is expected that there should be only one
@@ -123,12 +126,6 @@
     @Thunk boolean mWorkspaceLoaded;
     @Thunk boolean mAllAppsLoaded;
 
-    // When we are loading pages synchronously, we can't just post the binding of items on the side
-    // pages as this delays the rotation process.  Instead, we wait for a callback from the first
-    // draw (in Workspace) to initiate the binding of the remaining side pages.  Any time we start
-    // a normal load, we also clear this set of Runnables.
-    static final ArrayList<Runnable> mDeferredBindRunnables = new ArrayList<Runnable>();
-
     /**
      * Set of runnables to be called on the background thread after the workspace binding
      * is complete.
@@ -170,6 +167,9 @@
     // sBgWidgetProviders is the set of widget providers including custom internal widgets
     public static HashMap<ComponentKey, LauncherAppWidgetProviderInfo> sBgWidgetProviders;
 
+    // sBgShortcutProviders is the set of custom shortcut providers
+    public static List<ResolveInfo> sBgShortcutProviders;
+
     // sPendingPackages is a set of packages which could be on sdcard and are not available yet
     static final HashMap<UserHandleCompat, HashSet<String>> sPendingPackages =
             new HashMap<UserHandleCompat, HashSet<String>>();
@@ -184,6 +184,7 @@
     public interface Callbacks {
         public boolean setLoadOnResume();
         public int getCurrentWorkspaceScreen();
+        public void clearPendingBinds();
         public void startBinding();
         public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end,
                               boolean forceAnimateIcons);
@@ -208,6 +209,7 @@
         public void bindSearchProviderChanged();
         public boolean isAllAppsButtonRank(int rank);
         public void onPageBoundSynchronously(int page);
+        public void executeOnNextDraw(ViewOnDrawExecutor executor);
         public void dumpLogsToLocalData();
     }
 
@@ -259,7 +261,7 @@
 
     /** Runs the specified runnable immediately if called from the worker thread, otherwise it is
      * posted on the worker thread handler. */
-    private static void runOnWorkerThread(Runnable r) {
+    @Thunk static void runOnWorkerThread(Runnable r) {
         if (sWorkerThread.getThreadId() == Process.myTid()) {
             r.run();
         } else {
@@ -268,19 +270,6 @@
         }
     }
 
-    /**
-     * Runs the specified runnable after the loader is complete
-     */
-    @Thunk void runAfterBindCompletes(Runnable r) {
-        if (isLoadingWorkspace() || !mHasLoaderCompletedOnce) {
-            synchronized (mBindCompleteRunnables) {
-                mBindCompleteRunnables.add(r);
-            }
-        } else {
-            runOnWorkerThread(r);
-        }
-    }
-
     boolean canMigrateFromOldLauncherDb(Launcher launcher) {
         return mOldContentProviderExists && !launcher.isLauncherPreinstalled() ;
     }
@@ -599,11 +588,6 @@
                     "main thread");
         }
 
-        // Clear any deferred bind runnables
-        synchronized (mDeferredBindRunnables) {
-            mDeferredBindRunnables.clear();
-        }
-
         // Remove any queued UI runnables
         mHandler.cancelAll();
         // Unbind all the workspace items
@@ -889,8 +873,13 @@
     }
 
     private void assertWorkspaceLoaded() {
-        if (ProviderConfig.IS_DOGFOOD_BUILD && (isLoadingWorkspace() || !mHasLoaderCompletedOnce)) {
-            throw new RuntimeException("Trying to add shortcut while loader is running");
+        if (ProviderConfig.IS_DOGFOOD_BUILD) {
+            synchronized (mLock) {
+                if (!mHasLoaderCompletedOnce ||
+                        (mLoaderTask != null && mLoaderTask.mIsLoadingAndBindingWorkspace)) {
+                    throw new RuntimeException("Trying to add shortcut while loader is running");
+                }
+            }
         }
     }
 
@@ -1072,8 +1061,6 @@
 
     /**
      * Removes the specified item from the database
-     * @param context
-     * @param item
      */
     public static void deleteItemFromDatabase(Context context, final ItemInfo item) {
         ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
@@ -1083,8 +1070,6 @@
 
     /**
      * Removes the specified items from the database
-     * @param context
-     * @param item
      */
     static void deleteItemsFromDatabase(Context context, final ArrayList<? extends ItemInfo> items) {
         final ContentResolver cr = context.getContentResolver();
@@ -1175,9 +1160,9 @@
     }
 
     /**
-     * Remove the contents of the specified folder from the database
+     * Remove the specified folder and all its contents from the database.
      */
-    public static void deleteFolderContentsFromDatabase(Context context, final FolderInfo info) {
+    public static void deleteFolderAndContentsFromDatabase(Context context, final FolderInfo info) {
         final ContentResolver cr = context.getContentResolver();
 
         Runnable r = new Runnable() {
@@ -1350,14 +1335,16 @@
         // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
         InstallShortcutReceiver.enableInstallQueue();
         synchronized (mLock) {
-            // Clear any deferred bind-runnables from the synchronized load process
-            // We must do this before any loading/binding is scheduled below.
-            synchronized (mDeferredBindRunnables) {
-                mDeferredBindRunnables.clear();
-            }
-
             // Don't bother to start the thread if we know it's not going to do anything
             if (mCallbacks != null && mCallbacks.get() != null) {
+                final Callbacks oldCallbacks = mCallbacks.get();
+                // Clear any pending bind-runnables from the synchronized load process.
+                runOnMainThread(new Runnable() {
+                    public void run() {
+                        oldCallbacks.clearPendingBinds();
+                    }
+                });
+
                 // If there is already one running, tell it to stop.
                 stopLoaderLocked();
                 mLoaderTask = new LoaderTask(mApp.getContext(), loadFlags);
@@ -1372,31 +1359,6 @@
         }
     }
 
-    void bindRemainingSynchronousPages() {
-        // Post the remaining side pages to be loaded
-        if (!mDeferredBindRunnables.isEmpty()) {
-            Runnable[] deferredBindRunnables = null;
-            synchronized (mDeferredBindRunnables) {
-                deferredBindRunnables = mDeferredBindRunnables.toArray(
-                        new Runnable[mDeferredBindRunnables.size()]);
-                mDeferredBindRunnables.clear();
-            }
-            for (final Runnable r : deferredBindRunnables) {
-                mHandler.post(r);
-            }
-        }
-
-        // Run all the bind complete runnables after workspace is bound.
-        if (!mBindCompleteRunnables.isEmpty()) {
-            synchronized (mBindCompleteRunnables) {
-                for (final Runnable r : mBindCompleteRunnables) {
-                    runOnWorkerThread(r);
-                }
-                mBindCompleteRunnables.clear();
-            }
-        }
-    }
-
     public void stopLoader() {
         synchronized (mLock) {
             if (mLoaderTask != null) {
@@ -1436,15 +1398,6 @@
         return mAllAppsLoaded;
     }
 
-    boolean isLoadingWorkspace() {
-        synchronized (mLock) {
-            if (mLoaderTask != null) {
-                return mLoaderTask.isLoadingWorkspace();
-            }
-        }
-        return false;
-    }
-
     /**
      * Runnable for the thread that loads the contents of the launcher:
      *   - workspace icons
@@ -1463,10 +1416,6 @@
             mFlags = flags;
         }
 
-        boolean isLoadingWorkspace() {
-            return mIsLoadingAndBindingWorkspace;
-        }
-
         private void loadAndBindWorkspace() {
             mIsLoadingAndBindingWorkspace = true;
 
@@ -1638,11 +1587,12 @@
         }
 
         // check & update map of what's occupied; used to discard overlapping/invalid items
-        private boolean checkItemPlacement(LongArrayMap<ItemInfo[][]> occupied, ItemInfo item) {
+        private boolean checkItemPlacement(LongArrayMap<ItemInfo[][]> occupied, ItemInfo item,
+                   ArrayList<Long> workspaceScreens) {
             LauncherAppState app = LauncherAppState.getInstance();
             InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
-            final int countX = (int) profile.numColumns;
-            final int countY = (int) profile.numRows;
+            final int countX = profile.numColumns;
+            final int countY = profile.numRows;
 
             long containerIndex = item.screenId;
             if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
@@ -1684,7 +1634,12 @@
                     occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, items);
                     return true;
                 }
-            } else if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+            } else if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+                if (!workspaceScreens.contains((Long) item.screenId)) {
+                    // The item has an invalid screen id.
+                    return false;
+                }
+            } else {
                 // Skip further checking if it is not the hotseat or workspace container
                 return true;
             }
@@ -1750,8 +1705,8 @@
 
             LauncherAppState app = LauncherAppState.getInstance();
             InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
-            int countX = (int) profile.numColumns;
-            int countY = (int) profile.numRows;
+            int countX = profile.numColumns;
+            int countY = profile.numRows;
 
             if (MigrateFromRestoreTask.ENABLED && MigrateFromRestoreTask.shouldRunTask(mContext)) {
                 long migrationStartTime = System.currentTimeMillis();
@@ -1791,6 +1746,7 @@
                 clearSBgDataStructures();
                 final HashMap<String, Integer> installingPkgs = PackageInstallerCompat
                         .getInstance(mContext).updateAndGetActiveSessionCache();
+                sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
 
                 final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
                 final ArrayList<Long> restoredRows = new ArrayList<Long>();
@@ -1992,6 +1948,7 @@
                                 } catch (URISyntaxException e) {
                                     Launcher.addDumpLog(TAG,
                                             "Invalid uri: " + intentDescription, true);
+                                    itemsToRemove.add(id);
                                     continue;
                                 }
 
@@ -2062,7 +2019,7 @@
                                     }
 
                                     // check & update map of what's occupied
-                                    if (!checkItemPlacement(occupied, info)) {
+                                    if (!checkItemPlacement(occupied, info, sBgWorkspaceScreens)) {
                                         itemsToRemove.add(id);
                                         break;
                                     }
@@ -2113,7 +2070,7 @@
                                 folderInfo.options = c.getInt(optionsIndex);
 
                                 // check & update map of what's occupied
-                                if (!checkItemPlacement(occupied, folderInfo)) {
+                                if (!checkItemPlacement(occupied, folderInfo, sBgWorkspaceScreens)) {
                                     itemsToRemove.add(id);
                                     break;
                                 }
@@ -2233,13 +2190,14 @@
                                     if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
                                         container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
                                         Log.e(TAG, "Widget found where container != " +
-                                            "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
+                                                "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
+                                        itemsToRemove.add(id);
                                         continue;
                                     }
 
                                     appWidgetInfo.container = container;
                                     // check & update map of what's occupied
-                                    if (!checkItemPlacement(occupied, appWidgetInfo)) {
+                                    if (!checkItemPlacement(occupied, appWidgetInfo, sBgWorkspaceScreens)) {
                                         itemsToRemove.add(id);
                                         break;
                                     }
@@ -2328,8 +2286,6 @@
                             null, sWorker);
                 }
 
-                sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
-
                 // Remove any empty screens
                 ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
                 for (ItemInfo item: sBgItemsIdMap) {
@@ -2528,9 +2484,7 @@
                 final ArrayList<ItemInfo> workspaceItems,
                 final ArrayList<LauncherAppWidgetInfo> appWidgets,
                 final LongArrayMap<FolderInfo> folders,
-                ArrayList<Runnable> deferredBindRunnables) {
-
-            final boolean postOnMainThread = (deferredBindRunnables != null);
+                final Executor executor) {
 
             // Bind the workspace items
             int N = workspaceItems.size();
@@ -2547,13 +2501,7 @@
                         }
                     }
                 };
-                if (postOnMainThread) {
-                    synchronized (deferredBindRunnables) {
-                        deferredBindRunnables.add(r);
-                    }
-                } else {
-                    runOnMainThread(r);
-                }
+                executor.execute(r);
             }
 
             // Bind the folders
@@ -2566,13 +2514,7 @@
                         }
                     }
                 };
-                if (postOnMainThread) {
-                    synchronized (deferredBindRunnables) {
-                        deferredBindRunnables.add(r);
-                    }
-                } else {
-                    runOnMainThread(r);
-                }
+                executor.execute(r);
             }
 
             // Bind the widgets, one at a time
@@ -2587,11 +2529,7 @@
                         }
                     }
                 };
-                if (postOnMainThread) {
-                    deferredBindRunnables.add(r);
-                } else {
-                    runOnMainThread(r);
-                }
+                executor.execute(r);
             }
         }
 
@@ -2669,6 +2607,7 @@
                 public void run() {
                     Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                     if (callbacks != null) {
+                        callbacks.clearPendingBinds();
                         callbacks.startBinding();
                     }
                 }
@@ -2677,28 +2616,20 @@
 
             bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
 
-            // Load items on the current page
+            Executor mainExecutor = new DeferredMainThreadExecutor();
+            // Load items on the current page.
             bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets,
-                    currentFolders, null);
-            if (isLoadingSynchronously) {
-                r = new Runnable() {
-                    public void run() {
-                        Callbacks callbacks = tryGetCallbacks(oldCallbacks);
-                        if (callbacks != null && currentScreen != PagedView.INVALID_RESTORE_PAGE) {
-                            callbacks.onPageBoundSynchronously(currentScreen);
-                        }
-                    }
-                };
-                runOnMainThread(r);
-            }
+                    currentFolders, mainExecutor);
 
-            // Load all the remaining pages (if we are loading synchronously, we want to defer this
-            // work until after the first render)
-            synchronized (mDeferredBindRunnables) {
-                mDeferredBindRunnables.clear();
-            }
-            bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders,
-                    (isLoadingSynchronously ? mDeferredBindRunnables : null));
+            // In case of isLoadingSynchronously, only bind the first screen, and defer binding the
+            // remaining screens after first onDraw is called. This ensures that the first screen
+            // is immediately visible (eg. during rotation)
+            // In case of !isLoadingSynchronously, bind all pages one after other.
+            final Executor deferredExecutor = isLoadingSynchronously ?
+                    new ViewOnDrawExecutor(mHandler) : mainExecutor;
+
+            bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets,
+                    otherFolders, deferredExecutor);
 
             // Tell the workspace that we're done binding items
             r = new Runnable() {
@@ -2708,20 +2639,43 @@
                         callbacks.finishBindingItems();
                     }
 
+                    mIsLoadingAndBindingWorkspace = false;
+
+                    // Run all the bind complete runnables after workspace is bound.
+                    if (!mBindCompleteRunnables.isEmpty()) {
+                        synchronized (mBindCompleteRunnables) {
+                            for (final Runnable r : mBindCompleteRunnables) {
+                                runOnWorkerThread(r);
+                            }
+                            mBindCompleteRunnables.clear();
+                        }
+                    }
+
                     // If we're profiling, ensure this is the last thing in the queue.
                     if (DEBUG_LOADERS) {
                         Log.d(TAG, "bound workspace in "
                             + (SystemClock.uptimeMillis()-t) + "ms");
                     }
 
-                    mIsLoadingAndBindingWorkspace = false;
                 }
             };
+            deferredExecutor.execute(r);
+
             if (isLoadingSynchronously) {
-                synchronized (mDeferredBindRunnables) {
-                    mDeferredBindRunnables.add(r);
-                }
-            } else {
+                r = new Runnable() {
+                    public void run() {
+                        Callbacks callbacks = tryGetCallbacks(oldCallbacks);
+                        if (callbacks != null) {
+                            // We are loading synchronously, which means, some of the pages will be
+                            // bound after first draw. Inform the callbacks that page binding is
+                            // not complete, and schedule the remaining pages.
+                            if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
+                                callbacks.onPageBoundSynchronously(currentScreen);
+                            }
+                            callbacks.executeOnNextDraw((ViewOnDrawExecutor) deferredExecutor);
+                        }
+                    }
+                };
                 runOnMainThread(r);
             }
         }
@@ -2843,12 +2797,27 @@
 
                 final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
                 if (heuristic != null) {
-                    runAfterBindCompletes(new Runnable() {
+                    final Runnable r = new Runnable() {
 
                         @Override
                         public void run() {
                             heuristic.processUserApps(apps);
                         }
+                    };
+                    runOnMainThread(new Runnable() {
+
+                        @Override
+                        public void run() {
+                            // Check isLoadingWorkspace on the UI thread, as it is updated on
+                            // the UI thread.
+                            if (mIsLoadingAndBindingWorkspace) {
+                                synchronized (mBindCompleteRunnables) {
+                                    mBindCompleteRunnables.add(r);
+                                }
+                            } else {
+                                runOnWorkerThread(r);
+                            }
+                        }
                     });
                 }
             }
@@ -3315,9 +3284,18 @@
                     // Refresh widget list, if there is any newly added widget
                     PackageManager pm = context.getPackageManager();
                     for (String pkg : mPackages) {
-                        needToRefresh |= !pm.queryBroadcastReceivers(
-                                new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE)
-                                    .setPackage(pkg), 0).isEmpty();
+                        try {
+                            List<ResolveInfo> widgets = pm.queryBroadcastReceivers(
+                                    new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE)
+                                            .setPackage(pkg), 0);
+                            needToRefresh |= widgets != null && !widgets.isEmpty();
+                        } catch (RuntimeException e) {
+                            if (ProviderConfig.IS_DOGFOOD_BUILD) {
+                                throw e;
+                            }
+                            // Ignore the crash. We can live with a state widget list.
+                            Log.e(TAG, "PM call failed for " + pkg, e);
+                        }
                     }
                 }
 
@@ -3369,7 +3347,9 @@
                 return results;
             }
         } catch (Exception e) {
-            if (e.getCause() instanceof TransactionTooLargeException) {
+            if (!ProviderConfig.IS_DOGFOOD_BUILD &&
+                    (e.getCause() instanceof TransactionTooLargeException ||
+                    e.getCause() instanceof DeadObjectException)) {
                 // the returned value may be incomplete and will not be refreshed until the next
                 // time Launcher starts.
                 // TODO: after figuring out a repro step, introduce a dirty bit to check when
@@ -3429,8 +3409,29 @@
         PackageManager packageManager = mApp.getContext().getPackageManager();
         final ArrayList<Object> widgetsAndShortcuts = new ArrayList<Object>();
         widgetsAndShortcuts.addAll(getWidgetProviders(mApp.getContext(), refresh));
-        Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
-        widgetsAndShortcuts.addAll(packageManager.queryIntentActivities(shortcutsIntent, 0));
+
+        // Update shortcut providers
+        synchronized (sBgLock) {
+            try {
+                Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
+                List<ResolveInfo> providers = packageManager.queryIntentActivities(shortcutsIntent, 0);
+                sBgShortcutProviders = providers;
+            } catch (RuntimeException e) {
+                if (!ProviderConfig.IS_DOGFOOD_BUILD &&
+                        (e.getCause() instanceof TransactionTooLargeException ||
+                                e.getCause() instanceof DeadObjectException)) {
+                    /**
+                     * Ignore exception and use the cached list if available.
+                     * Refer to {@link #getWidgetProviders(Context, boolean}} for more info.
+                     */
+                } else {
+                    throw e;
+                }
+            }
+            if (sBgShortcutProviders != null) {
+                widgetsAndShortcuts.addAll(sBgShortcutProviders);
+            }
+        }
         mBgWidgetsModel.setWidgetsAndShortcuts(widgetsAndShortcuts);
     }
 
@@ -3732,6 +3733,14 @@
         }
     }
 
+    @Thunk class DeferredMainThreadExecutor implements Executor {
+
+        @Override
+        public void execute(Runnable command) {
+            runOnMainThread(command);
+        }
+    }
+
     /**
      * @return the looper for the worker thread which can be used to start background tasks.
      */
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 44a43e1..c6827da 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -65,12 +65,11 @@
 import java.util.List;
 
 public class LauncherProvider extends ContentProvider {
-    private static final String TAG = "Launcher.LauncherProvider";
+    private static final String TAG = "LauncherProvider";
     private static final boolean LOGD = false;
 
     private static final int DATABASE_VERSION = 26;
 
-    static final String OLD_AUTHORITY = "com.android.launcher2.settings";
     public static final String AUTHORITY = ProviderConfig.AUTHORITY;
 
     static final String TABLE_FAVORITES = LauncherSettings.Favorites.TABLE_NAME;
@@ -139,7 +138,7 @@
     }
 
     private void reloadLauncherIfExternal() {
-        if (Binder.getCallingPid() != Process.myPid()) {
+        if (Utilities.ATLEAST_MARSHMALLOW && Binder.getCallingPid() != Process.myPid()) {
             LauncherAppState app = LauncherAppState.getInstanceNoCreate();
             if (app != null) {
                 app.reloadWorkspace();
@@ -166,7 +165,20 @@
         uri = ContentUris.withAppendedId(uri, rowId);
         notifyListeners();
 
-        reloadLauncherIfExternal();
+        if (Utilities.ATLEAST_MARSHMALLOW) {
+            reloadLauncherIfExternal();
+        } else {
+            // Deprecated behavior to support legacy devices which rely on provider callbacks.
+            LauncherAppState app = LauncherAppState.getInstanceNoCreate();
+            if (app != null && "true".equals(uri.getQueryParameter("isExternalAdd"))) {
+                app.reloadWorkspace();
+            }
+
+            String notify = uri.getQueryParameter("notify");
+            if (notify == null || "true".equals(notify)) {
+                getContext().getContentResolver().notifyChange(uri, null);
+            }
+        }
         return uri;
     }
 
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
index 3d1efc6..d3af19a 100644
--- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java
+++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
@@ -23,7 +23,9 @@
 import android.animation.PropertyValuesHolder;
 import android.animation.TimeInterpolator;
 import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
 import android.content.res.Resources;
+import android.os.Build;
 import android.util.Log;
 import android.view.View;
 import android.view.animation.AccelerateInterpolator;
@@ -174,7 +176,7 @@
     }
 
     /**
-     * Starts and animation to the workspace from the current overlay view.
+     * Starts an animation to the workspace from the current overlay view.
      */
     public void startAnimationToWorkspace(final Launcher.State fromState,
             final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
@@ -188,9 +190,13 @@
         if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED) {
             startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState,
                     animated, onCompleteRunnable);
-        } else {
+        } else if (fromState == Launcher.State.WIDGETS ||
+                fromState == Launcher.State.WIDGETS_SPRING_LOADED) {
             startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState,
                     animated, onCompleteRunnable);
+        } else {
+            startAnimationToNewWorkspaceState(fromWorkspaceState, toWorkspaceState,
+                    animated, onCompleteRunnable);
         }
     }
 
@@ -361,7 +367,7 @@
                         if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
                             v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
                         }
-                        if (Utilities.ATLEAST_LOLLIPOP && Utilities.isViewAttachedToWindow(v)) {
+                        if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
                             v.buildLayer();
                         }
                     }
@@ -401,7 +407,7 @@
     }
 
     /**
-     * Starts and animation to the workspace from the apps view.
+     * Starts an animation to the workspace from the apps view.
      */
     private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState,
             final Workspace.State toWorkspaceState, final boolean animated, 
@@ -446,10 +452,10 @@
     }
 
     /**
-     * Starts and animation to the workspace from the widgets view.
+     * Starts an animation to the workspace from the widgets view.
      */
     private void startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState,
-            final Workspace.State toWorkspaceState, final boolean animated, 
+            final Workspace.State toWorkspaceState, final boolean animated,
             final Runnable onCompleteRunnable) {
         final WidgetsContainerView widgetsView = mLauncher.getWidgetsView();
         PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
@@ -475,6 +481,95 @@
     }
 
     /**
+     * Starts an animation to the workspace from another workspace state, e.g. normal to overview.
+     */
+    private void startAnimationToNewWorkspaceState(final Workspace.State fromWorkspaceState,
+            final Workspace.State toWorkspaceState, final boolean animated,
+            final Runnable onCompleteRunnable) {
+        final View fromWorkspace = mLauncher.getWorkspace();
+        final HashMap<View, Integer> layerViews = new HashMap<>();
+        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
+        final int revealDuration = mLauncher.getResources()
+                .getInteger(R.integer.config_overlayRevealTime);
+
+        // Cancel the current animation
+        cancelAnimation();
+
+        // Create the workspace animation.
+        // NOTE: this call apparently also sets the state for the workspace if !animated
+        Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState,
+                animated, layerViews);
+
+        startWorkspaceSearchBarAnimation(animation, fromWorkspaceState, toWorkspaceState,
+                animated ? revealDuration : 0, null);
+
+        if (animated) {
+            if (workspaceAnim != null) {
+                animation.play(workspaceAnim);
+            }
+            dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, true);
+
+            final AnimatorSet stateAnimation = animation;
+            final Runnable startAnimRunnable = new Runnable() {
+                @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+                public void run() {
+                    // Check that mCurrentAnimation hasn't changed while
+                    // we waited for a layout/draw pass
+                    if (mCurrentAnimation != stateAnimation)
+                        return;
+
+                    dispatchOnLauncherTransitionStart(fromWorkspace, animated, true);
+
+                    // Enable all necessary layers
+                    for (View v : layerViews.keySet()) {
+                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
+                            v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+                        }
+                        if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
+                            v.buildLayer();
+                        }
+                    }
+                    stateAnimation.start();
+                }
+            };
+            animation.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true);
+
+                    // Run any queued runnables
+                    if (onCompleteRunnable != null) {
+                        onCompleteRunnable.run();
+                    }
+
+                    // Disable all necessary layers
+                    for (View v : layerViews.keySet()) {
+                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
+                            v.setLayerType(View.LAYER_TYPE_NONE, null);
+                        }
+                    }
+
+                    // This can hold unnecessary references to views.
+                    cleanupAnimation();
+                }
+            });
+            fromWorkspace.post(startAnimRunnable);
+            mCurrentAnimation = animation;
+        } else /* if (!animated) */ {
+            dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, true);
+            dispatchOnLauncherTransitionStart(fromWorkspace, animated, true);
+            dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true);
+
+            // Run any queued runnables
+            if (onCompleteRunnable != null) {
+                onCompleteRunnable.run();
+            }
+
+            mCurrentAnimation = null;
+        }
+    }
+
+    /**
      * Creates and starts a new animation to the workspace.
      */
     private AnimatorSet startAnimationToWorkspaceFromOverlay(final Workspace.State fromWorkspaceState,
@@ -656,6 +751,7 @@
 
             final AnimatorSet stateAnimation = animation;
             final Runnable startAnimRunnable = new Runnable() {
+                @TargetApi(Build.VERSION_CODES.LOLLIPOP)
                 public void run() {
                     // Check that mCurrentAnimation hasn't changed while
                     // we waited for a layout/draw pass
@@ -670,7 +766,7 @@
                         if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
                             v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
                         }
-                        if (Utilities.ATLEAST_LOLLIPOP && Utilities.isViewAttachedToWindow(v)) {
+                        if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
                             v.buildLayer();
                         }
                     }
@@ -680,7 +776,7 @@
             fromView.post(startAnimRunnable);
 
             return animation;
-        } else {
+        } else /* if (!(animated && initialized)) */ {
             fromView.setVisibility(View.GONE);
             dispatchOnLauncherTransitionPrepare(fromView, animated, true);
             dispatchOnLauncherTransitionStart(fromView, animated, true);
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 3702cda..e4d6448 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -63,7 +63,7 @@
     // the min drag distance for a fling to register, to prevent random page shifts
     private static final int MIN_LENGTH_FOR_FLING = 25;
 
-    protected static final int PAGE_SNAP_ANIMATION_DURATION = 750;
+    public static final int PAGE_SNAP_ANIMATION_DURATION = 750;
     protected static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950;
 
     private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f;
@@ -372,7 +372,10 @@
     }
 
     /**
-     * Returns the index of the currently displayed page.
+     * Returns the index of the currently displayed page. When in free scroll mode, this is the page
+     * that the user was on before entering free scroll mode (e.g. the home screen page they
+     * long-pressed on to enter the overview). Try using {@link #getPageNearestToCenterOfScreen()}
+     * to get the page the user is currently scrolling over.
      */
     public int getCurrentPage() {
         return mCurrentPage;
@@ -381,7 +384,7 @@
     /**
      * Returns the index of page to be shown immediately afterwards.
      */
-    int getNextPage() {
+    public int getNextPage() {
         return (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
     }
 
@@ -438,7 +441,7 @@
                     Math.min(newPage, mTempVisiblePagesRange[1]));
         }
         // Ensure that it is clamped by the actual set of children in all cases
-        validatedPage = Math.max(0, Math.min(validatedPage, getPageCount() - 1));
+        validatedPage = Utilities.boundInRange(validatedPage, 0, getPageCount() - 1);
         return validatedPage;
     }
 
@@ -1039,11 +1042,10 @@
 
         if (pageCount > 0) {
             int viewportWidth = getViewportWidth();
-            int curScreen = 0;
+            int lastVisiblePageIndex = 0;
 
-            int count = getChildCount();
-            for (int i = 0; i < count; i++) {
-                View currPage = getPageAt(i);
+            for (int currPageIndex = 0; currPageIndex < pageCount; currPageIndex++) {
+                View currPage = getPageAt(currPageIndex);
 
                 sTmpIntPoint[0] = 0;
                 Utilities.getDescendantCoordRelativeToParent(currPage, this, sTmpIntPoint, false);
@@ -1064,13 +1066,13 @@
                         break;
                     }
                 }
-                curScreen = i;
                 if (range[0] < 0) {
-                    range[0] = curScreen;
+                    range[0] = currPageIndex;
                 }
+                lastVisiblePageIndex = currPageIndex;
             }
 
-            range[1] = curScreen;
+            range[1] = lastVisiblePageIndex;
         } else {
             range[0] = -1;
             range[1] = -1;
@@ -2300,7 +2302,7 @@
     }
 
     protected String getCurrentPageDescription() {
-        return String.format(getContext().getString(R.string.default_scroll_format),
+        return getContext().getString(R.string.default_scroll_format,
                 getNextPage() + 1, getChildCount());
     }
 
diff --git a/src/com/android/launcher3/SearchDropTargetBar.java b/src/com/android/launcher3/SearchDropTargetBar.java
index f54d9d8..878a474 100644
--- a/src/com/android/launcher3/SearchDropTargetBar.java
+++ b/src/com/android/launcher3/SearchDropTargetBar.java
@@ -23,16 +23,15 @@
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.accessibility.AccessibilityManager;
-import android.view.animation.AccelerateInterpolator;
-import android.widget.FrameLayout;
 
+import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.util.Thunk;
 
 /*
- * Ths bar will manage the transition between the QSB search bar and the delete drop
- * targets so that each of the individual IconDropTargets don't have to.
+ * This bar will manage the transition between the QSB search bar and the delete/uninstall drop
+ * targets so that each of the individual ButtonDropTargets don't have to.
  */
-public class SearchDropTargetBar extends FrameLayout implements DragController.DragListener {
+public class SearchDropTargetBar extends BaseDropTargetBar {
 
     /** The different states that the search bar space can be in. */
     public enum State {
@@ -57,21 +56,13 @@
         }
     }
 
-    private static int DEFAULT_DRAG_FADE_DURATION = 175;
 
-    private LauncherViewPropertyAnimator mDropTargetBarAnimator;
     private LauncherViewPropertyAnimator mQSBSearchBarAnimator;
-    private static final AccelerateInterpolator sAccelerateInterpolator =
-            new AccelerateInterpolator();
 
     private State mState = State.SEARCH_BAR;
     @Thunk View mQSB;
-    @Thunk View mDropTargetBar;
-    private boolean mDeferOnDragEnd = false;
-    @Thunk boolean mAccessibilityEnabled = false;
 
     // Drop targets
-    private ButtonDropTarget mInfoDropTarget;
     private ButtonDropTarget mDeleteDropTarget;
     private ButtonDropTarget mUninstallDropTarget;
 
@@ -83,61 +74,48 @@
         super(context, attrs, defStyle);
     }
 
-    public void setup(Launcher launcher, DragController dragController) {
-        dragController.addDragListener(this);
-        dragController.setFlingToDeleteDropTarget(mDeleteDropTarget);
-
-        dragController.addDragListener(mInfoDropTarget);
-        dragController.addDragListener(mDeleteDropTarget);
-        dragController.addDragListener(mUninstallDropTarget);
-
-        dragController.addDropTarget(mInfoDropTarget);
-        dragController.addDropTarget(mDeleteDropTarget);
-        dragController.addDropTarget(mUninstallDropTarget);
-
-        mInfoDropTarget.setLauncher(launcher);
-        mDeleteDropTarget.setLauncher(launcher);
-        mUninstallDropTarget.setLauncher(launcher);
-    }
-
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
 
         // Get the individual components
-        mDropTargetBar = findViewById(R.id.drag_target_bar);
-        mInfoDropTarget = (ButtonDropTarget) mDropTargetBar.findViewById(R.id.info_target_text);
         mDeleteDropTarget = (ButtonDropTarget) mDropTargetBar.findViewById(R.id.delete_target_text);
-        mUninstallDropTarget = (ButtonDropTarget) mDropTargetBar.findViewById(R.id.uninstall_target_text);
+        mUninstallDropTarget = (ButtonDropTarget) mDropTargetBar
+                .findViewById(R.id.uninstall_target_text);
 
-        mInfoDropTarget.setSearchDropTargetBar(this);
-        mDeleteDropTarget.setSearchDropTargetBar(this);
-        mUninstallDropTarget.setSearchDropTargetBar(this);
+        mDeleteDropTarget.setDropTargetBar(this);
+        mUninstallDropTarget.setDropTargetBar(this);
+    }
 
-        // Create the various fade animations
-        mDropTargetBar.setAlpha(0f);
-        mDropTargetBarAnimator = new LauncherViewPropertyAnimator(mDropTargetBar);
-        mDropTargetBarAnimator.setInterpolator(sAccelerateInterpolator);
-        mDropTargetBarAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationStart(Animator animation) {
-                // Ensure that the view is visible for the animation
-                mDropTargetBar.setVisibility(View.VISIBLE);
-            }
+    @Override
+    public void setup(Launcher launcher, DragController dragController) {
+        dragController.addDragListener(this);
+        dragController.setFlingToDeleteDropTarget(mDeleteDropTarget);
 
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                if (mDropTargetBar != null) {
-                    AlphaUpdateListener.updateVisibility(mDropTargetBar, mAccessibilityEnabled);
-                }
-            }
-        });
+        dragController.addDragListener(mDeleteDropTarget);
+        dragController.addDragListener(mUninstallDropTarget);
+
+        dragController.addDropTarget(mDeleteDropTarget);
+        dragController.addDropTarget(mUninstallDropTarget);
+
+        mDeleteDropTarget.setLauncher(launcher);
+        mUninstallDropTarget.setLauncher(launcher);
+    }
+
+    @Override
+    public void showDropTarget() {
+        animateToState(State.DROP_TARGET, DEFAULT_DRAG_FADE_DURATION);
+    }
+
+    @Override
+    public void hideDropTarget() {
+        animateToState(State.SEARCH_BAR, DEFAULT_DRAG_FADE_DURATION);
     }
 
     public void setQsbSearchBar(View qsb) {
         mQSB = qsb;
         if (mQSB != null) {
-            // Update the search ber animation
+            // Update the search bar animation
             mQSBSearchBarAnimator = new LauncherViewPropertyAnimator(mQSB);
             mQSBSearchBarAnimator.setInterpolator(sAccelerateInterpolator);
             mQSBSearchBarAnimator.addListener(new AnimatorListenerAdapter() {
@@ -182,46 +160,6 @@
     }
 
     /**
-     * Convenience method to animate the alpha of a view using hardware layers.
-     */
-    private void animateViewAlpha(LauncherViewPropertyAnimator animator, View v, float alpha,
-            int duration) {
-        if (v != null && Float.compare(v.getAlpha(), alpha) != 0) {
-            if (duration > 0) {
-                animator.alpha(alpha).withLayer().setDuration(duration).start();
-            } else {
-                v.setAlpha(alpha);
-                AlphaUpdateListener.updateVisibility(v, mAccessibilityEnabled);
-            }
-        }
-    }
-
-    /*
-     * DragController.DragListener implementation
-     */
-    @Override
-    public void onDragStart(DragSource source, ItemInfo info, int dragAction) {
-        animateToState(State.DROP_TARGET, DEFAULT_DRAG_FADE_DURATION);
-    }
-
-    /**
-     * This is called to defer hiding the delete drop target until the drop animation has completed,
-     * instead of hiding immediately when the drag has ended.
-     */
-    public void deferOnDragEnd() {
-        mDeferOnDragEnd = true;
-    }
-
-    @Override
-    public void onDragEnd() {
-        if (!mDeferOnDragEnd) {
-            animateToState(State.SEARCH_BAR, DEFAULT_DRAG_FADE_DURATION);
-        } else {
-            mDeferOnDragEnd = false;
-        }
-    }
-
-    /**
      * @return the bounds of the QSB search bar.
      */
     public Rect getSearchBarBounds() {
@@ -240,11 +178,11 @@
         }
     }
 
+    @Override
     public void enableAccessibleDrag(boolean enable) {
         if (mQSB != null) {
             mQSB.setVisibility(enable ? View.GONE : View.VISIBLE);
         }
-        mInfoDropTarget.enableAccessibleDrag(enable);
         mDeleteDropTarget.enableAccessibleDrag(enable);
         mUninstallDropTarget.enableAccessibleDrag(enable);
     }
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index f2c46de..a86a2f9 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -70,7 +70,7 @@
     /**
      * The intent used to start the application.
      */
-    Intent intent;
+    public Intent intent;
 
     /**
      * Indicates whether the icon comes from an application's resource (if false)
diff --git a/src/com/android/launcher3/SimpleOnStylusPressListener.java b/src/com/android/launcher3/SimpleOnStylusPressListener.java
new file mode 100644
index 0000000..6b97dce
--- /dev/null
+++ b/src/com/android/launcher3/SimpleOnStylusPressListener.java
@@ -0,0 +1,25 @@
+package com.android.launcher3;
+
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.launcher3.StylusEventHelper.StylusButtonListener;
+
+/**
+ * Simple listener that performs a long click on the view after a stylus button press.
+ */
+public class SimpleOnStylusPressListener implements StylusButtonListener {
+    private View mView;
+
+    public SimpleOnStylusPressListener(View view) {
+        mView = view;
+    }
+
+    public boolean onPressed(MotionEvent event) {
+        return mView.isLongClickable() && mView.performLongClick();
+    }
+
+    public boolean onReleased(MotionEvent event) {
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/StylusEventHelper.java b/src/com/android/launcher3/StylusEventHelper.java
index 54c5509..d5fc0fa 100644
--- a/src/com/android/launcher3/StylusEventHelper.java
+++ b/src/com/android/launcher3/StylusEventHelper.java
@@ -6,55 +6,82 @@
 
 /**
  * Helper for identifying when a stylus touches a view while the primary stylus button is pressed.
- * This can occur in {@value MotionEvent#ACTION_DOWN} or {@value MotionEvent#ACTION_MOVE}. On a
- * stylus button press this performs the view's {@link View#performLongClick()} method, if the view
- * is long clickable.
+ * This can occur in {@value MotionEvent#ACTION_DOWN} or {@value MotionEvent#ACTION_MOVE}.
  */
 public class StylusEventHelper {
-    private boolean mIsButtonPressed;
-    private View mView;
-
-    public StylusEventHelper(View view) {
-        mView = view;
-    }
 
     /**
-     * Call this in onTouchEvent method of a view to identify a stylus button press and perform a
-     * long click (if the view is long clickable).
-     *
-     * @param event The event to check for a stylus button press.
-     * @return Whether a stylus event occurred and was handled.
+     * Implement this interface to receive callbacks for a stylus button press and release.
      */
-    public boolean checkAndPerformStylusEvent(MotionEvent event) {
-        final float slop = ViewConfiguration.get(mView.getContext()).getScaledTouchSlop();
+    public interface StylusButtonListener {
+        /**
+         * Called when the stylus button is pressed.
+         *
+         * @param event The MotionEvent that the button press occurred for.
+         * @return Whether the event was handled.
+         */
+        public boolean onPressed(MotionEvent event);
 
-        if (!mView.isLongClickable()) {
-            // We don't do anything unless the view is long clickable.
-            return false;
+        /**
+         * Called when the stylus button is released after a button press. This is also called if
+         * the event is canceled or the stylus is lifted off the screen.
+         *
+         * @param event The MotionEvent the button release occurred for.
+         * @return Whether the event was handled.
+         */
+        public boolean onReleased(MotionEvent event);
+    }
+
+    private boolean mIsButtonPressed;
+    private View mView;
+    private StylusButtonListener mListener;
+    private final float mSlop;
+
+    /**
+     * Constructs a helper for listening to stylus button presses and releases. Ensure that {
+     * {@link #onMotionEvent(MotionEvent)} and {@link #onGenericMotionEvent(MotionEvent)} are called on
+     * the helper to correctly identify stylus events.
+     *
+     * @param listener The listener to call for stylus events.
+     * @param view Optional view associated with the touch events.
+     */
+    public StylusEventHelper(StylusButtonListener listener, View view) {
+        mListener = listener;
+        mView = view;
+        if (mView != null) {
+            mSlop = ViewConfiguration.get(mView.getContext()).getScaledTouchSlop();
+        } else {
+            mSlop = ViewConfiguration.getTouchSlop();
         }
+    }
 
+    public boolean onMotionEvent(MotionEvent event) {
         final boolean stylusButtonPressed = isStylusButtonPressed(event);
         switch (event.getAction()) {
             case MotionEvent.ACTION_DOWN:
-                mIsButtonPressed = false;
-                if (stylusButtonPressed && mView.performLongClick()) {
-                    mIsButtonPressed = true;
-                    return true;
+                mIsButtonPressed = stylusButtonPressed;
+                if (mIsButtonPressed) {
+                    return mListener.onPressed(event);
                 }
                 break;
             case MotionEvent.ACTION_MOVE:
-                if (Utilities.pointInView(mView, event.getX(), event.getY(), slop)) {
-                    if (!mIsButtonPressed && stylusButtonPressed && mView.performLongClick()) {
-                        mIsButtonPressed = true;
-                        return true;
-                    } else if (mIsButtonPressed && !stylusButtonPressed) {
-                        mIsButtonPressed = false;
-                    }
+                if (!Utilities.pointInView(mView, event.getX(), event.getY(), mSlop)) {
+                    return false;
+                }
+                if (!mIsButtonPressed && stylusButtonPressed) {
+                    mIsButtonPressed = true;
+                    return mListener.onPressed(event);
+                } else if (mIsButtonPressed && !stylusButtonPressed) {
+                    mIsButtonPressed = false;
+                    return mListener.onReleased(event);
                 }
                 break;
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
-                mIsButtonPressed = false;
+                if (mIsButtonPressed) {
+                    mIsButtonPressed = false;
+                    return mListener.onReleased(event);
+                }
                 break;
         }
         return false;
@@ -74,8 +101,9 @@
      * @param event The event to check.
      * @return Whether a stylus button press occurred.
      */
-    public static boolean isStylusButtonPressed(MotionEvent event) {
+    private static boolean isStylusButtonPressed(MotionEvent event) {
         return event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS
-                && event.isButtonPressed(MotionEvent.BUTTON_SECONDARY);
+                && ((event.getButtonState() & MotionEvent.BUTTON_SECONDARY)
+                        == MotionEvent.BUTTON_SECONDARY);
     }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/UninstallDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java
index 9ed4fb6..7388161 100644
--- a/src/com/android/launcher3/UninstallDropTarget.java
+++ b/src/com/android/launcher3/UninstallDropTarget.java
@@ -82,14 +82,17 @@
     void completeDrop(final DragObject d) {
         final Pair<ComponentName, Integer> componentInfo = getAppInfoFlags(d.dragInfo);
         final UserHandleCompat user = d.dragInfo.user;
-        if (startUninstallActivity(mLauncher, d.dragInfo)) {
+        if (startActivityWithUninstallAffordance(d)) {
 
             final Runnable checkIfUninstallWasSuccess = new Runnable() {
                 @Override
                 public void run() {
-                    String packageName = componentInfo.first.getPackageName();
-                    boolean uninstallSuccessful = !AllAppsList.packageHasActivities(
-                            getContext(), packageName, user);
+                    boolean uninstallSuccessful = false;
+                    if (componentInfo != null) {
+                        String packageName = componentInfo.first.getPackageName();
+                        uninstallSuccessful = !AllAppsList.packageHasActivities(
+                                getContext(), packageName, user);
+                    }
                     sendUninstallResult(d.dragSource, uninstallSuccessful);
                 }
             };
@@ -99,6 +102,10 @@
         }
     }
 
+    protected boolean startActivityWithUninstallAffordance(DragObject d) {
+        return startUninstallActivity(mLauncher, d.dragInfo);
+    }
+
     public static boolean startUninstallActivity(Launcher launcher, ItemInfo info) {
         final Pair<ComponentName, Integer> componentInfo = getAppInfoFlags(info);
         final UserHandleCompat user = info.user;
@@ -115,7 +122,7 @@
     /**
      * Interface defining an object that can provide uninstallable drag objects.
      */
-    public static interface UninstallSource {
+    public interface UninstallSource {
 
         /**
          * A pending uninstall operation was complete.
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index e0a4a1c..4e93684 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -119,10 +119,9 @@
         return Log.isLoggable(propertyName, Log.VERBOSE);
     }
 
-    public static boolean isAllowRotationPrefEnabled(Context context, boolean multiProcess) {
+    public static boolean isAllowRotationPrefEnabled(Context context) {
         SharedPreferences sharedPrefs = context.getSharedPreferences(
-                LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE | (multiProcess ?
-                        Context.MODE_MULTI_PROCESS : 0));
+                LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
         boolean allowRotationPref = sharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY, false);
         return sForceEnableRotation || allowRotationPref;
     }
@@ -529,16 +528,6 @@
         return null;
     }
 
-    @TargetApi(Build.VERSION_CODES.KITKAT)
-    public static boolean isViewAttachedToWindow(View v) {
-        if (ATLEAST_KITKAT) {
-            return v.isAttachedToWindow();
-        } else {
-            // A proxy call which returns null, if the view is not attached to the window.
-            return v.getKeyDispatcherState() != null;
-        }
-    }
-
     /**
      * Returns a widget with category {@link AppWidgetProviderInfo#WIDGET_CATEGORY_SEARCHBOX}
      * provided by the same package which is set to be global search activity.
@@ -737,4 +726,13 @@
             return true;
         }
     }
+
+    /**
+     * Ensures that a value is within given bounds. Specifically:
+     * If value is less than lowerBound, return lowerBound; else if value is greater than upperBound,
+     * return upperBound; else return value unchanged.
+     */
+    public static int boundInRange(int value, int lowerBound, int upperBound) {
+        return Math.max(lowerBound, Math.min(value, upperBound));
+    }
 }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index f299a45..745a19c 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -48,7 +48,6 @@
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.Choreographer;
-import android.view.Display;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
@@ -67,6 +66,11 @@
 import com.android.launcher3.accessibility.OverviewScreenAccessibilityDelegate;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.dragndrop.DragScroller;
+import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.dragndrop.SpringLoadedDragController;
 import com.android.launcher3.util.LongArrayMap;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.WallpaperUtils;
@@ -135,9 +139,6 @@
     private int mDragOverX = -1;
     private int mDragOverY = -1;
 
-    private static Rect mLandscapeCellLayoutMetrics = null;
-    private static Rect mPortraitCellLayoutMetrics = null;
-
     CustomContentCallbacks mCustomContentCallbacks;
     boolean mCustomContentShowing;
     private float mLastCustomContentScrollProgress = -1f;
@@ -270,19 +271,13 @@
     boolean mStartedSendingScrollEvents;
     boolean mShouldSendPageSettled;
     int mLastOverlaySroll = 0;
+    private boolean mForceDrawAdjacentPages = false;
 
     // Handles workspace state transitions
     private WorkspaceStateTransitionAnimation mStateTransitionAnimation;
 
     private AccessibilityDelegate mPagesAccessibilityDelegate;
 
-    private final Runnable mBindPages = new Runnable() {
-        @Override
-        public void run() {
-            mLauncher.getModel().bindRemainingSynchronousPages();
-        }
-    };
-
     /**
      * Used to inflate the Workspace from XML.
      *
@@ -567,6 +562,7 @@
         CellLayout customScreen = (CellLayout)
                 mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, this, false);
         customScreen.disableDragTarget();
+        customScreen.disableJailContent();
 
         mWorkspaceScreens.put(CUSTOM_CONTENT_SCREEN_ID, customScreen);
         mScreenOrder.add(0, CUSTOM_CONTENT_SCREEN_ID);
@@ -1107,9 +1103,9 @@
                     LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
                     LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) info.hostView;
                     if (lahv != null && lahv.isReinflateRequired()) {
-                        mLauncher.removeAppWidget(info);
-                        // Remove the current widget which is inflated with the wrong orientation
-                        cl.removeView(lahv);
+                        // Remove and rebind the current widget (which was inflated in the wrong
+                        // orientation), but don't delete it from the database
+                        mLauncher.removeItem(lahv, null, info, false  /* deleteFromDb */);
                         mLauncher.bindAppWidget(info);
                     }
                 }
@@ -1399,7 +1395,7 @@
             return false;
         }
 
-        private float wallpaperOffsetForCurrentScroll() {
+        public float wallpaperOffsetForScroll(int scroll) {
             // TODO: do different behavior if it's  a live wallpaper?
             // Don't use up all the wallpaper parallax until you have at least
             // MIN_PARALLAX_PAGE_SPAN pages
@@ -1438,7 +1434,7 @@
                 // Sometimes the left parameter of the pages is animated during a layout transition;
                 // this parameter offsets it to keep the wallpaper from animating as well
                 int adjustedScroll =
-                        getScrollX() - firstPageScrollX - getLayoutTransitionOffsetForPage(0);
+                        scroll - firstPageScrollX - getLayoutTransitionOffsetForPage(0);
                 float offset = Math.min(1, adjustedScroll / (float) scrollRange);
                 offset = Math.max(0, offset);
 
@@ -1452,6 +1448,10 @@
             }
         }
 
+        private float wallpaperOffsetForCurrentScroll() {
+            return wallpaperOffsetForScroll(getScrollX());
+        }
+
         private int numEmptyScreensToIgnore() {
             int numScrollingPages = getChildCount() - numCustomPages();
             if (numScrollingPages >= MIN_PARALLAX_PAGE_SPAN && hasExtraEmptyScreen()) {
@@ -1582,6 +1582,7 @@
             setOnClickListener(mLauncher);
         }
         mLauncher.getSearchDropTargetBar().enableAccessibleDrag(enable);
+        mLauncher.getAppInfoDropTargetBar().enableAccessibleDrag(enable);
         mLauncher.getHotseat().getLayout()
             .enableAccessibleDrag(enable, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
     }
@@ -1595,7 +1596,7 @@
     }
 
     public boolean isOnOrMovingToCustomContent() {
-        return hasCustomContent() && getNextPage() == 0;
+        return hasCustomContent() && getNextPage() == 0 && mRestorePage == INVALID_RESTORE_PAGE;
     }
 
     private void updateStateForCustomContent(int screenCenter) {
@@ -1712,14 +1713,6 @@
     }
 
     @Override
-    protected void onDraw(Canvas canvas) {
-        super.onDraw(canvas);
-
-        // Call back to LauncherModel to finish binding after the first draw
-        post(mBindPages);
-    }
-
-    @Override
     protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
         if (!mLauncher.isAppsViewVisible()) {
             final Folder openFolder = getOpenFolder();
@@ -1848,10 +1841,10 @@
     @Override
     protected void getVisiblePages(int[] range) {
         super.getVisiblePages(range);
-        if (mState == State.OVERVIEW || mState == State.SPRING_LOADED) {
+        if (mForceDrawAdjacentPages) {
             // In overview mode, make sure that the two side pages are visible.
-            range[0] = Math.min(range[0], Math.max(getCurrentPage() - 1, numCustomPages()));
-            range[1] = Math.max(range[0], Math.min(getCurrentPage() + 1, getPageCount() - 1));
+            range[0] = Utilities.boundInRange(getCurrentPage() - 1, numCustomPages(), range[1]);
+            range[1] = Utilities.boundInRange(getCurrentPage() + 1, range[0], getPageCount() - 1);
         }
     }
 
@@ -1920,6 +1913,18 @@
     }
 
     public void onDragStartedWithItem(PendingAddItemInfo info, Bitmap b, boolean clipAlpha) {
+        // Find a page that has enough space to place this widget (after rearranging/resizing).
+        // Start at the current page and search right (on LTR) until finding a page with enough
+        // space. Since an empty screen is the furthest right, a page must be found.
+        int currentPageInOverview = getPageNearestToCenterOfScreen();
+        for (int pageIndex = currentPageInOverview; pageIndex < getPageCount(); pageIndex++) {
+            CellLayout page = (CellLayout) getPageAt(pageIndex);
+            if (page.hasReorderSolution(info)) {
+                setCurrentPage(pageIndex);
+                break;
+            }
+        }
+
         int[] size = estimateItemSize(info, false);
 
         // The outline is used to visualize where the item will land if dropped
@@ -1994,6 +1999,10 @@
         return -workspaceOffsetTopEdge + overviewOffsetTopEdge;
     }
 
+    int getSpringLoadedTranslationY() {
+        return getOverviewModeTranslationY();
+    }
+
     /**
      * Sets the current workspace {@link State}, returning an animation transitioning the workspace
      * to that new state.
@@ -2009,7 +2018,8 @@
         updateAccessibilityFlags();
         if (mState == State.OVERVIEW || mState == State.SPRING_LOADED) {
             // Redraw pages, as we might want to draw pages which were not visible.
-            invalidate();
+            mForceDrawAdjacentPages = true;
+            invalidate(); // This will call dispatchDraw(), which calls getVisiblePages().
         }
 
         return workspaceAnim;
@@ -2083,6 +2093,7 @@
         mIsSwitchingState = false;
         updateChildrenLayersEnabled(false);
         showCustomContentIfNecessary();
+        mForceDrawAdjacentPages = false;
     }
 
     void updateCustomContentVisibility() {
@@ -2325,16 +2336,18 @@
             throw new IllegalStateException(msg);
         }
 
+        if (child.getParent() instanceof ShortcutAndWidgetContainer) {
+            mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
+        }
+
         DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source,
                 (ItemInfo) dragObject, DragController.DRAG_ACTION_MOVE, dragVisualizeOffset,
                 dragRect, scale, accessible);
         dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
 
-        if (child.getParent() instanceof ShortcutAndWidgetContainer) {
-            mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
-        }
-
         b.recycle();
+
+        mLauncher.enterSpringLoadedDragMode();
     }
 
     public void beginExternalDragShared(View child, DragSource source) {
@@ -2383,6 +2396,8 @@
 
         // Recycle temporary bitmaps
         tmpB.recycle();
+
+        mLauncher.enterSpringLoadedDragMode();
     }
 
     public boolean transitionStateShouldAllowDrop() {
@@ -2840,49 +2855,6 @@
         CellLayout layout = getCurrentDropLayout();
         setCurrentDropLayout(layout);
         setCurrentDragOverlappingLayout(layout);
-
-        if (!workspaceInModalState()) {
-            mLauncher.getDragLayer().showPageHints();
-        }
-    }
-
-    /** Return a rect that has the cellWidth/cellHeight (left, top), and
-     * widthGap/heightGap (right, bottom) */
-    static Rect getCellLayoutMetrics(Launcher launcher, int orientation) {
-        LauncherAppState app = LauncherAppState.getInstance();
-        InvariantDeviceProfile inv = app.getInvariantDeviceProfile();
-
-        Display display = launcher.getWindowManager().getDefaultDisplay();
-        Point smallestSize = new Point();
-        Point largestSize = new Point();
-        display.getCurrentSizeRange(smallestSize, largestSize);
-        int countX = (int) inv.numColumns;
-        int countY = (int) inv.numRows;
-        boolean isLayoutRtl = Utilities.isRtl(launcher.getResources());
-        if (orientation == CellLayout.LANDSCAPE) {
-            if (mLandscapeCellLayoutMetrics == null) {
-                Rect padding = inv.landscapeProfile.getWorkspacePadding(isLayoutRtl);
-                int width = largestSize.x - padding.left - padding.right;
-                int height = smallestSize.y - padding.top - padding.bottom;
-                mLandscapeCellLayoutMetrics = new Rect();
-                mLandscapeCellLayoutMetrics.set(
-                        DeviceProfile.calculateCellWidth(width, countX),
-                        DeviceProfile.calculateCellHeight(height, countY), 0, 0);
-            }
-            return mLandscapeCellLayoutMetrics;
-        } else if (orientation == CellLayout.PORTRAIT) {
-            if (mPortraitCellLayoutMetrics == null) {
-                Rect padding = inv.portraitProfile.getWorkspacePadding(isLayoutRtl);
-                int width = smallestSize.x - padding.left - padding.right;
-                int height = largestSize.y - padding.top - padding.bottom;
-                mPortraitCellLayoutMetrics = new Rect();
-                mPortraitCellLayoutMetrics.set(
-                        DeviceProfile.calculateCellWidth(width, countX),
-                        DeviceProfile.calculateCellHeight(height, countY), 0, 0);
-            }
-            return mPortraitCellLayoutMetrics;
-        }
-        return null;
     }
 
     @Override
@@ -2917,8 +2889,6 @@
         setCurrentDragOverlappingLayout(null);
 
         mSpringLoadedDragController.cancel();
-
-        mLauncher.getDragLayer().hidePageHints();
     }
 
     private void enfoceDragParity(String event, int update, int expectedValue) {
@@ -3753,6 +3723,9 @@
         }
         mDragOutline = null;
         mDragInfo = null;
+
+        mLauncher.exitSpringLoadedDragModeDelayed(success,
+                Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
     }
 
     /**
@@ -3842,7 +3815,7 @@
 
     @Override
     public boolean supportsAppInfoDropTarget() {
-        return false;
+        return true;
     }
 
     @Override
@@ -4036,7 +4009,7 @@
 
             @Override
             public boolean evaluate(ItemInfo info, View v, View parent) {
-                return info.id == id;
+                return info != null && info.id == id;
             }
         });
     }
@@ -4415,7 +4388,7 @@
 
     private String getPageDescription(int page) {
         int delta = numCustomPages();
-        return String.format(getContext().getString(R.string.workspace_scroll_format),
+        return getContext().getString(R.string.workspace_scroll_format,
                 page + 1 - delta, getChildCount() - delta);
     }
 
@@ -4466,12 +4439,9 @@
 
             for (LauncherAppWidgetInfo info : mInfos) {
                 if (info.hostView instanceof PendingAppWidgetHostView) {
+                    // Remove and rebind the current widget, but don't delete it from the database
                     PendingAppWidgetHostView view = (PendingAppWidgetHostView) info.hostView;
-                    mLauncher.removeAppWidget(info);
-
-                    CellLayout cl = (CellLayout) view.getParent().getParent();
-                    // Remove the current widget
-                    cl.removeView(view);
+                    mLauncher.removeItem(view, null, info, false /* deleteFromDb */);
                     mLauncher.bindAppWidget(info);
                 }
             }
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index f097585..d32ce73 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -30,6 +30,7 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.DecelerateInterpolator;
 
+import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.util.Thunk;
 
 import java.util.HashMap;
@@ -284,8 +285,13 @@
         float finalHotseatAndPageIndicatorAlpha = (states.stateIsNormal || states.stateIsSpringLoaded) ?
                 1f : 0f;
         float finalOverviewPanelAlpha = states.stateIsOverview ? 1f : 0f;
-        float finalWorkspaceTranslationY = states.stateIsOverview || states.stateIsOverviewHidden ?
-                mWorkspace.getOverviewModeTranslationY() : 0;
+
+        float finalWorkspaceTranslationY = 0;
+        if (states.stateIsOverview || states.stateIsOverviewHidden) {
+            finalWorkspaceTranslationY = mWorkspace.getOverviewModeTranslationY();
+        } else if (states.stateIsSpringLoaded) {
+            finalWorkspaceTranslationY = mWorkspace.getSpringLoadedTranslationY();
+        }
 
         final int childCount = mWorkspace.getChildCount();
         final int customPageCount = mWorkspace.numCustomPages();
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index c11aab9..9479685 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -20,7 +20,6 @@
 import com.android.launcher3.AppWidgetResizeFrame;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeleteDropTarget;
-import com.android.launcher3.DragController.DragListener;
 import com.android.launcher3.DragSource;
 import com.android.launcher3.Folder;
 import com.android.launcher3.FolderInfo;
@@ -36,6 +35,7 @@
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.UninstallDropTarget;
 import com.android.launcher3.Workspace;
+import com.android.launcher3.dragndrop.DragController.DragListener;
 import com.android.launcher3.util.Thunk;
 
 import java.util.ArrayList;
diff --git a/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java b/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java
new file mode 100644
index 0000000..dafa73f
--- /dev/null
+++ b/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2015 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.allapps;
+
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+
+import android.view.Gravity;
+import com.android.launcher3.R;
+
+/**
+ * A helper class to positon and orient a drawable to be drawn.
+ */
+class TransformedImageDrawable {
+    private Drawable mImage;
+    private float mXPercent;
+    private float mYPercent;
+    private int mGravity;
+    private int mAlpha;
+
+    /**
+     * @param gravity If one of the Gravity center values, the x and y offset will take the width
+     *                and height of the image into account to center the image to the offset.
+     */
+    public TransformedImageDrawable(Resources res, int resourceId, float xPct, float yPct,
+            int gravity) {
+        mImage = res.getDrawable(resourceId);
+        mXPercent = xPct;
+        mYPercent = yPct;
+        mGravity = gravity;
+    }
+
+    public void setAlpha(int alpha) {
+        mImage.setAlpha(alpha);
+        mAlpha = alpha;
+    }
+
+    public int getAlpha() {
+        return mAlpha;
+    }
+
+    public void updateBounds(Rect bounds) {
+        int width = mImage.getIntrinsicWidth();
+        int height = mImage.getIntrinsicHeight();
+        int left = bounds.left + (int) (mXPercent * bounds.width());
+        int top = bounds.top + (int) (mYPercent * bounds.height());
+        if ((mGravity & Gravity.CENTER_HORIZONTAL) == Gravity.CENTER_HORIZONTAL) {
+            left -= (width / 2);
+        }
+        if ((mGravity & Gravity.CENTER_VERTICAL) == Gravity.CENTER_VERTICAL) {
+            top -= (height / 2);
+        }
+        mImage.setBounds(left, top, left + width, top + height);
+    }
+
+    public void draw(Canvas canvas) {
+        int c = canvas.save(Canvas.MATRIX_SAVE_FLAG);
+        mImage.draw(canvas);
+        canvas.restoreToCount(c);
+    }
+}
+
+/**
+ * This is a custom composite drawable that has a fixed virtual size and dynamically lays out its
+ * children images relatively within its bounds.  This way, we can reduce the memory usage of a
+ * single, large sparsely populated image.
+ */
+public class AllAppsBackgroundDrawable extends Drawable {
+
+    private final TransformedImageDrawable mHand;
+    private final TransformedImageDrawable[] mIcons;
+    private final int mWidth;
+    private final int mHeight;
+
+    private ObjectAnimator mBackgroundAnim;
+
+    public AllAppsBackgroundDrawable(Context context) {
+        Resources res = context.getResources();
+        mHand = new TransformedImageDrawable(res, R.drawable.ic_all_apps_bg_hand,
+                0.575f, 0.1f, Gravity.CENTER_HORIZONTAL);
+        mIcons = new TransformedImageDrawable[4];
+        mIcons[0] = new TransformedImageDrawable(res, R.drawable.ic_all_apps_bg_icon_1,
+                0.375f, 0, Gravity.CENTER_HORIZONTAL);
+        mIcons[1] = new TransformedImageDrawable(res, R.drawable.ic_all_apps_bg_icon_2,
+                0.3125f, 0.25f, Gravity.CENTER_HORIZONTAL);
+        mIcons[2] = new TransformedImageDrawable(res, R.drawable.ic_all_apps_bg_icon_3,
+                0.475f, 0.4f, Gravity.CENTER_HORIZONTAL);
+        mIcons[3] = new TransformedImageDrawable(res, R.drawable.ic_all_apps_bg_icon_4,
+                0.7f, 0.125f, Gravity.CENTER_HORIZONTAL);
+        mWidth = res.getDimensionPixelSize(R.dimen.all_apps_background_canvas_width);
+        mHeight = res.getDimensionPixelSize(R.dimen.all_apps_background_canvas_height);
+    }
+
+    /**
+     * Animates the background alpha.
+     */
+    public void animateBgAlpha(float finalAlpha, int duration) {
+        int finalAlphaI = (int) (finalAlpha * 255f);
+        if (getAlpha() != finalAlphaI) {
+            mBackgroundAnim = cancelAnimator(mBackgroundAnim);
+            mBackgroundAnim = ObjectAnimator.ofInt(this, "alpha", finalAlphaI);
+            mBackgroundAnim.setDuration(duration);
+            mBackgroundAnim.start();
+        }
+    }
+
+    /**
+     * Sets the background alpha immediately.
+     */
+    public void setBgAlpha(float finalAlpha) {
+        int finalAlphaI = (int) (finalAlpha * 255f);
+        if (getAlpha() != finalAlphaI) {
+            mBackgroundAnim = cancelAnimator(mBackgroundAnim);
+            setAlpha(finalAlphaI);
+        }
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return mWidth;
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return mHeight;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        mHand.draw(canvas);
+        for (int i = 0; i < mIcons.length; i++) {
+            mIcons[i].draw(canvas);
+        }
+    }
+
+    @Override
+    protected void onBoundsChange(Rect bounds) {
+        super.onBoundsChange(bounds);
+        mHand.updateBounds(bounds);
+        for (int i = 0; i < mIcons.length; i++) {
+            mIcons[i].updateBounds(bounds);
+        }
+        invalidateSelf();
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        mHand.setAlpha(alpha);
+        for (int i = 0; i < mIcons.length; i++) {
+            mIcons[i].setAlpha(alpha);
+        }
+        invalidateSelf();
+    }
+
+    @Override
+    public int getAlpha() {
+        return mHand.getAlpha();
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+        // Do nothing
+    }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.TRANSLUCENT;
+    }
+
+    private ObjectAnimator cancelAnimator(ObjectAnimator animator) {
+        if (animator != null) {
+            animator.removeAllListeners();
+            animator.cancel();
+        }
+        return null;
+    }
+}
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index f5e06c5..979a733 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -290,6 +290,15 @@
         }
     }
 
+    /**
+     * Resets the state of AllApps.
+     */
+    public void reset() {
+        // Reset the search bar and base recycler view after transitioning home
+        mSearchBarController.reset();
+        mAppsRecyclerView.reset();
+    }
+
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
@@ -468,8 +477,6 @@
 
         // Start the drag
         mLauncher.getWorkspace().beginDragShared(v, mIconLastTouchPos, this, false);
-        // Enter spring loaded mode
-        mLauncher.enterSpringLoadedDragMode();
 
         return false;
     }
@@ -555,8 +562,7 @@
     @Override
     public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
         if (toWorkspace) {
-            // Reset the search bar after transitioning home
-            mSearchBarController.reset();
+            reset();
         }
     }
 
@@ -614,13 +620,14 @@
         if (apps != null) {
             mApps.setOrderedFilter(apps);
             mAdapter.setLastSearchQuery(query);
-            mAppsRecyclerView.scrollToTop();
+            mAppsRecyclerView.onSearchResultsChanged();
         }
     }
 
     @Override
     public void clearSearchResult() {
         mApps.setOrderedFilter(null);
+        mAppsRecyclerView.onSearchResultsChanged();
 
         // Clear the search query
         mSearchQueryBuilder.clear();
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index affd32a..f885567 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -24,11 +24,13 @@
 import android.graphics.Paint;
 import android.graphics.PointF;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.support.v4.view.accessibility.AccessibilityRecordCompat;
 import android.support.v4.view.accessibility.AccessibilityEventCompat;
 import android.net.Uri;
 import android.support.v7.widget.GridLayoutManager;
 import android.support.v7.widget.RecyclerView;
+import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewConfiguration;
@@ -412,11 +414,10 @@
      */
     public void setLastSearchQuery(String query) {
         Resources res = mLauncher.getResources();
-        String formatStr = res.getString(R.string.all_apps_no_search_results);
         mLastSearchQuery = query;
-        mEmptySearchMessage = String.format(formatStr, query);
+        mEmptySearchMessage = res.getString(R.string.all_apps_no_search_results, query);
         if (mMarketAppName != null) {
-            mMarketSearchMessage = String.format(res.getString(R.string.all_apps_search_market_message),
+            mMarketSearchMessage = res.getString(R.string.all_apps_search_market_message,
                     mMarketAppName);
             mMarketSearchIntent = createMarketSearchIntent(query);
         }
@@ -511,14 +512,17 @@
             case EMPTY_SEARCH_VIEW_TYPE:
                 TextView emptyViewText = (TextView) holder.mContent;
                 emptyViewText.setText(mEmptySearchMessage);
+                emptyViewText.setGravity(mApps.hasNoFilteredResults() ? Gravity.CENTER :
+                        Gravity.START | Gravity.CENTER_VERTICAL);
                 break;
             case SEARCH_MARKET_VIEW_TYPE:
-                View searchView = holder.mContent;
+                TextView searchView = (TextView) holder.mContent;
                 if (mMarketSearchIntent != null) {
                     searchView.setVisibility(View.VISIBLE);
                     searchView.setContentDescription(mMarketSearchMessage);
-                    ((TextView) searchView.findViewById(R.id.search_market_text))
-                            .setText(mMarketSearchMessage);
+                    searchView.setGravity(mApps.hasNoFilteredResults() ? Gravity.CENTER :
+                            Gravity.START | Gravity.CENTER_VERTICAL);
+                    searchView.setText(mMarketSearchMessage);
                 } else {
                     searchView.setVisibility(View.GONE);
                 }
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index a46af5e..10d10f1 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -15,8 +15,11 @@
  */
 package com.android.launcher3.allapps;
 
+import android.animation.ObjectAnimator;
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
@@ -26,7 +29,9 @@
 import com.android.launcher3.BaseRecyclerView;
 import com.android.launcher3.BaseRecyclerViewFastScrollBar;
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
 import com.android.launcher3.Stats;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.util.Thunk;
 
 import java.util.List;
@@ -56,6 +61,9 @@
 
     private ScrollPositionState mScrollPosState = new ScrollPositionState();
 
+    private AllAppsBackgroundDrawable mEmptySearchBackground;
+    private int mEmptySearchBackgroundTopOffset;
+
     public AllAppsRecyclerView(Context context) {
         this(context, null);
     }
@@ -71,6 +79,11 @@
     public AllAppsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr);
+
+        Resources res = getResources();
+        mScrollbar.setDetachThumbOnFastScroll();
+        mEmptySearchBackgroundTopOffset = res.getDimensionPixelSize(
+                R.dimen.all_apps_empty_search_bg_top_offset);
     }
 
     /**
@@ -100,6 +113,10 @@
      * Scrolls this recycler view to the top.
      */
     public void scrollToTop() {
+        // Ensure we reattach the scrollbar if it was previously detached while fast-scrolling
+        if (mScrollbar.isThumbDetached()) {
+            mScrollbar.reattachThumbToScroll();
+        }
         scrollToPosition(0);
     }
 
@@ -116,6 +133,30 @@
     }
 
     @Override
+    public void onDraw(Canvas c) {
+        // Draw the background
+        if (mEmptySearchBackground != null && mEmptySearchBackground.getAlpha() > 0) {
+            c.clipRect(mBackgroundPadding.left, mBackgroundPadding.top,
+                    getWidth() - mBackgroundPadding.right,
+                    getHeight() - mBackgroundPadding.bottom);
+
+            mEmptySearchBackground.draw(c);
+        }
+
+        super.onDraw(c);
+    }
+
+    @Override
+    protected boolean verifyDrawable(Drawable who) {
+        return who == mEmptySearchBackground || super.verifyDrawable(who);
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        updateEmptySearchBackgroundBounds();
+    }
+
+    @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
 
@@ -135,6 +176,25 @@
         }
     }
 
+    public void onSearchResultsChanged() {
+        // Always scroll the view to the top so the user can see the changed results
+        scrollToTop();
+
+        if (mApps.hasNoFilteredResults()) {
+            if (mEmptySearchBackground == null) {
+                mEmptySearchBackground = new AllAppsBackgroundDrawable(getContext());
+                mEmptySearchBackground.setAlpha(0);
+                mEmptySearchBackground.setCallback(this);
+                updateEmptySearchBackgroundBounds();
+            }
+            mEmptySearchBackground.animateBgAlpha(1f, 150);
+        } else if (mEmptySearchBackground != null) {
+            // For the time being, we just immediately hide the background to ensure that it does
+            // not overlap with the results
+            mEmptySearchBackground.setBgAlpha(0f);
+        }
+    }
+
     /**
      * Maps the touch (from 0..1) to the adapter position that should be visible.
      */
@@ -167,8 +227,8 @@
         }
 
         // Map the touch position back to the scroll of the recycler view
-        getCurScrollState(mScrollPosState, mApps.getAdapterItems());
-        int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight, 0);
+        getCurScrollState(mScrollPosState);
+        int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight);
         LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
         if (mFastScrollMode == FAST_SCROLL_MODE_FREE_SCROLL) {
             layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction));
@@ -215,24 +275,83 @@
      * Updates the bounds for the scrollbar.
      */
     @Override
-    public void onUpdateScrollbar() {
+    public void onUpdateScrollbar(int dy) {
         List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
 
         // Skip early if there are no items or we haven't been measured
         if (items.isEmpty() || mNumAppsPerRow == 0) {
-            mScrollbar.setScrollbarThumbOffset(-1, -1);
+            mScrollbar.setThumbOffset(-1, -1);
             return;
         }
 
         // Find the index and height of the first visible row (all rows have the same height)
         int rowCount = mApps.getNumAppRows();
-        getCurScrollState(mScrollPosState, items);
+        getCurScrollState(mScrollPosState);
         if (mScrollPosState.rowIndex < 0) {
-            mScrollbar.setScrollbarThumbOffset(-1, -1);
+            mScrollbar.setThumbOffset(-1, -1);
             return;
         }
 
-        synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount, 0);
+        // Only show the scrollbar if there is height to be scrolled
+        int availableScrollBarHeight = getAvailableScrollBarHeight();
+        int availableScrollHeight = getAvailableScrollHeight(mApps.getNumAppRows(), mScrollPosState.rowHeight);
+        if (availableScrollHeight <= 0) {
+            mScrollbar.setThumbOffset(-1, -1);
+            return;
+        }
+
+        // Calculate the current scroll position, the scrollY of the recycler view accounts for the
+        // view padding, while the scrollBarY is drawn right up to the background padding (ignoring
+        // padding)
+        int scrollY = getPaddingTop() +
+                (mScrollPosState.rowIndex * mScrollPosState.rowHeight) - mScrollPosState.rowTopOffset;
+        int scrollBarY = mBackgroundPadding.top +
+                (int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
+
+        if (mScrollbar.isThumbDetached()) {
+            int scrollBarX;
+            if (Utilities.isRtl(getResources())) {
+                scrollBarX = mBackgroundPadding.left;
+            } else {
+                scrollBarX = getWidth() - mBackgroundPadding.right - mScrollbar.getThumbWidth();
+            }
+
+            if (mScrollbar.isDraggingThumb()) {
+                // If the thumb is detached, then just update the thumb to the current
+                // touch position
+                mScrollbar.setThumbOffset(scrollBarX, (int) mScrollbar.getLastTouchY());
+            } else {
+                int thumbScrollY = mScrollbar.getThumbOffset().y;
+                int diffScrollY = scrollBarY - thumbScrollY;
+                if (diffScrollY * dy > 0f) {
+                    // User is scrolling in the same direction the thumb needs to catch up to the
+                    // current scroll position.  We do this by mapping the difference in movement
+                    // from the original scroll bar position to the difference in movement necessary
+                    // in the detached thumb position to ensure that both speed towards the same
+                    // position at either end of the list.
+                    if (dy < 0) {
+                        int offset = (int) ((dy * thumbScrollY) / (float) scrollBarY);
+                        thumbScrollY += Math.max(offset, diffScrollY);
+                    } else {
+                        int offset = (int) ((dy * (availableScrollBarHeight - thumbScrollY)) /
+                                (float) (availableScrollBarHeight - scrollBarY));
+                        thumbScrollY += Math.min(offset, diffScrollY);
+                    }
+                    thumbScrollY = Math.max(0, Math.min(availableScrollBarHeight, thumbScrollY));
+                    mScrollbar.setThumbOffset(scrollBarX, thumbScrollY);
+                    if (scrollBarY == thumbScrollY) {
+                        mScrollbar.reattachThumbToScroll();
+                    }
+                } else {
+                    // User is scrolling in an opposite direction to the direction that the thumb
+                    // needs to catch up to the scroll position.  Do nothing except for updating
+                    // the scroll bar x to match the thumb width.
+                    mScrollbar.setThumbOffset(scrollBarX, thumbScrollY);
+                }
+            }
+        } else {
+            synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount);
+        }
     }
 
     /**
@@ -284,13 +403,13 @@
     /**
      * Returns the current scroll state of the apps rows.
      */
-    private void getCurScrollState(ScrollPositionState stateOut,
-            List<AlphabeticalAppsList.AdapterItem> items) {
+    protected void getCurScrollState(ScrollPositionState stateOut) {
         stateOut.rowIndex = -1;
         stateOut.rowTopOffset = -1;
         stateOut.rowHeight = -1;
 
         // Return early if there are no items or we haven't been measured
+        List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
         if (items.isEmpty() || mNumAppsPerRow == 0) {
             return;
         }
@@ -312,6 +431,13 @@
         }
     }
 
+    @Override
+    protected boolean supportsFastScrolling() {
+        // Only allow fast scrolling when the user is not searching, since the results are not
+        // grouped in a meaningful order
+        return !mApps.hasFilter();
+    }
+
     /**
      * Returns the scrollY for the given position in the adapter.
      */
@@ -325,4 +451,20 @@
             return 0;
         }
     }
+
+    /**
+     * Updates the bounds of the empty search background.
+     */
+    private void updateEmptySearchBackgroundBounds() {
+        if (mEmptySearchBackground == null) {
+            return;
+        }
+
+        // Center the empty search background on this new view bounds
+        int x = (getMeasuredWidth() - mEmptySearchBackground.getIntrinsicWidth()) / 2;
+        int y = mEmptySearchBackgroundTopOffset;
+        mEmptySearchBackground.setBounds(x, y,
+                x + mEmptySearchBackground.getIntrinsicWidth(),
+                y + mEmptySearchBackground.getIntrinsicHeight());
+    }
 }
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index cf53aa6..cb989e5 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -43,6 +43,11 @@
     private static final boolean DEBUG = false;
     private static final boolean DEBUG_PREDICTIONS = false;
 
+    private static final int FAST_SCROLL_FRACTION_DISTRIBUTE_BY_ROWS_FRACTION = 0;
+    private static final int FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS = 1;
+
+    private final int mFastScrollDistributionMode = FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS;
+
     /**
      * Info about a section in the alphabetic list
      */
@@ -85,8 +90,6 @@
         /** Section & App properties */
         // The section for this item
         public SectionInfo sectionInfo;
-        // The row that this item shows up on
-        public int rowIndex;
 
         /** App-only properties */
         // The section name of this app.  Note that there can be multiple items with different
@@ -94,6 +97,8 @@
         public String sectionName = null;
         // The index of this app in the section
         public int sectionAppIndex = -1;
+        // The row that this item shows up on
+        public int rowIndex;
         // The index of this app in the row
         public int rowAppIndex;
         // The associated AppInfo for the app
@@ -515,18 +520,36 @@
             }
             mNumAppRowsInAdapter = rowIndex + 1;
 
-            // Pre-calculate all the fast scroller fractions based on the number of rows
-            float rowFraction = 1f / mNumAppRowsInAdapter;
-            for (FastScrollSectionInfo info : mFastScrollerSections) {
-                AdapterItem item = info.fastScrollToItem;
-                if (item.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE &&
-                        item.viewType != AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) {
-                    info.touchFraction = 0f;
-                    continue;
-                }
+            // Pre-calculate all the fast scroller fractions
+            switch (mFastScrollDistributionMode) {
+                case FAST_SCROLL_FRACTION_DISTRIBUTE_BY_ROWS_FRACTION:
+                    float rowFraction = 1f / mNumAppRowsInAdapter;
+                    for (FastScrollSectionInfo info : mFastScrollerSections) {
+                        AdapterItem item = info.fastScrollToItem;
+                        if (item.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE &&
+                                item.viewType != AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) {
+                            info.touchFraction = 0f;
+                            continue;
+                        }
 
-                float subRowFraction = item.rowAppIndex * (rowFraction / mNumAppsPerRow);
-                info.touchFraction = item.rowIndex * rowFraction + subRowFraction;
+                        float subRowFraction = item.rowAppIndex * (rowFraction / mNumAppsPerRow);
+                        info.touchFraction = item.rowIndex * rowFraction + subRowFraction;
+                    }
+                    break;
+                case FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS:
+                    float perSectionTouchFraction = 1f / mFastScrollerSections.size();
+                    float cumulativeTouchFraction = 0f;
+                    for (FastScrollSectionInfo info : mFastScrollerSections) {
+                        AdapterItem item = info.fastScrollToItem;
+                        if (item.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE &&
+                                item.viewType != AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) {
+                            info.touchFraction = 0f;
+                            continue;
+                        }
+                        info.touchFraction = cumulativeTouchFraction;
+                        cumulativeTouchFraction += perSectionTouchFraction;
+                    }
+                    break;
             }
         }
 
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVL.java b/src/com/android/launcher3/compat/UserManagerCompatVL.java
index 4d404db..dc3ec3c 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatVL.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatVL.java
@@ -26,6 +26,7 @@
 import android.os.UserHandle;
 
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.util.LongArrayMap;
 
 import java.util.ArrayList;
@@ -100,7 +101,9 @@
 
     @Override
     public long getUserCreationTime(UserHandleCompat user) {
-        // TODO: Use system API once available.
+        if (Utilities.ATLEAST_MARSHMALLOW) {
+            return mUserManager.getUserCreationTime(user.getUser());
+        }
         SharedPreferences prefs = mContext.getSharedPreferences(
                 LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
         String key = USER_CREATION_TIME_KEY + getSerialNumberForUser(user);
diff --git a/src/com/android/launcher3/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
similarity index 87%
rename from src/com/android/launcher3/DragController.java
rename to src/com/android/launcher3/dragndrop/DragController.java
index ea41f3f..4a39e6b 100644
--- a/src/com/android/launcher3/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.launcher3;
+package com.android.launcher3.dragndrop;
 
 import android.content.ComponentName;
 import android.content.Context;
@@ -35,8 +35,17 @@
 import android.view.ViewConfiguration;
 import android.view.inputmethod.InputMethodManager;
 
+import com.android.launcher3.DeleteDropTarget;
+import com.android.launcher3.DragSource;
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.PagedView;
+import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.util.Thunk;
 
+import com.android.launcher3.R;
+
 import java.util.ArrayList;
 import java.util.HashSet;
 
@@ -60,9 +69,9 @@
     private static final int SCROLL_OUTSIDE_ZONE = 0;
     private static final int SCROLL_WAITING_IN_ZONE = 1;
 
-    static final int SCROLL_NONE = -1;
-    static final int SCROLL_LEFT = 0;
-    static final int SCROLL_RIGHT = 1;
+    public static final int SCROLL_NONE = -1;
+    public static final int SCROLL_LEFT = 0;
+    public static final int SCROLL_RIGHT = 1;
 
     private static final float MAX_FLING_DEGREES = 35f;
 
@@ -72,7 +81,6 @@
     // temporaries to avoid gc thrash
     private Rect mRectTemp = new Rect();
     private final int[] mCoordinatesTemp = new int[2];
-    private final boolean mIsRtl;
 
     /**
      * Drag driver for the current drag/drop operation, or null if there is no active DND operation.
@@ -89,11 +97,6 @@
     /** Y coordinate of the down event. */
     private int mMotionDownY;
 
-    /** the area at the edge of the screen that makes the workspace go left
-     *   or right while you're dragging.
-     */
-    private final int mScrollZone;
-
     private DropTarget.DragObject mDragObject;
 
     /** Who can receive drop events */
@@ -104,9 +107,6 @@
     /** The window token used as the parent for the DragView. */
     private IBinder mWindowToken;
 
-    /** The view that will be scrolled when dragging to the left and right edges of the screen. */
-    private View mScrollView;
-
     private View mMoveTarget;
 
     @Thunk DragScroller mDragScroller;
@@ -119,7 +119,6 @@
 
     @Thunk int mLastTouch[] = new int[2];
     @Thunk long mLastTouchUpTime = -1;
-    @Thunk int mDistanceSinceScroll = 0;
 
     private int mTmpPoint[] = new int[2];
     private Rect mDragLayerRect = new Rect();
@@ -149,20 +148,15 @@
 
     /**
      * Used to create a new DragLayer from XML.
-     *
-     * @param context The application's context.
      */
     public DragController(Launcher launcher) {
         Resources r = launcher.getResources();
         mLauncher = launcher;
         mHandler = new Handler();
-        mScrollZone = r.getDimensionPixelSize(R.dimen.scroll_zone);
         mVelocityTracker = VelocityTracker.obtain();
 
-        float density = r.getDisplayMetrics().density;
         mFlingToDeleteThresholdVelocity =
-                (int) (r.getInteger(R.integer.config_flingToDeleteMinVelocity) * density);
-        mIsRtl = Utilities.isRtl(r);
+                r.getDimensionPixelSize(R.dimen.drag_flingToDeleteMinVelocity);
     }
 
     /**
@@ -420,7 +414,7 @@
         return mTmpPoint;
     }
 
-    long getLastGestureUpTime() {
+    public long getLastGestureUpTime() {
         if (mDragDriver != null) {
             return System.currentTimeMillis();
         } else {
@@ -428,7 +422,7 @@
         }
     }
 
-    void resetLastGestureUpTime() {
+    public void resetLastGestureUpTime() {
         mLastTouchUpTime = -1;
     }
 
@@ -536,7 +530,7 @@
     /**
      * Sets the view that should handle move events.
      */
-    void setMoveTarget(View view) {
+    public void setMoveTarget(View view) {
         mMoveTarget = view;
     }    
 
@@ -550,7 +544,6 @@
             mScrollState = SCROLL_OUTSIDE_ZONE;
             mScrollRunnable.setDirection(SCROLL_RIGHT);
             mDragScroller.onExitScrollArea();
-            mLauncher.getDragLayer().onExitScrollArea();
         }
     }
 
@@ -564,11 +557,8 @@
         mDragObject.y = coordinates[1];
         checkTouchMove(dropTarget);
 
-        // Check if we are hovering over the scroll areas
-        mDistanceSinceScroll += Math.hypot(mLastTouch[0] - x, mLastTouch[1] - y);
         mLastTouch[0] = x;
         mLastTouch[1] = y;
-        checkScrollState(x, y);
     }
 
     public void forceTouchMove() {
@@ -596,36 +586,6 @@
         mLastDropTarget = dropTarget;
     }
 
-    @Thunk void checkScrollState(int x, int y) {
-        final int slop = ViewConfiguration.get(mLauncher).getScaledWindowTouchSlop();
-        final int delay = mDistanceSinceScroll < slop ? RESCROLL_DELAY : SCROLL_DELAY;
-        final DragLayer dragLayer = mLauncher.getDragLayer();
-        final int forwardDirection = mIsRtl ? SCROLL_RIGHT : SCROLL_LEFT;
-        final int backwardsDirection = mIsRtl ? SCROLL_LEFT : SCROLL_RIGHT;
-
-        if (x < mScrollZone) {
-            if (mScrollState == SCROLL_OUTSIDE_ZONE) {
-                mScrollState = SCROLL_WAITING_IN_ZONE;
-                if (mDragScroller.onEnterScrollArea(x, y, forwardDirection)) {
-                    dragLayer.onEnterScrollArea(forwardDirection);
-                    mScrollRunnable.setDirection(forwardDirection);
-                    mHandler.postDelayed(mScrollRunnable, delay);
-                }
-            }
-        } else if (x > mScrollView.getWidth() - mScrollZone) {
-            if (mScrollState == SCROLL_OUTSIDE_ZONE) {
-                mScrollState = SCROLL_WAITING_IN_ZONE;
-                if (mDragScroller.onEnterScrollArea(x, y, backwardsDirection)) {
-                    dragLayer.onEnterScrollArea(backwardsDirection);
-                    mScrollRunnable.setDirection(backwardsDirection);
-                    mHandler.postDelayed(mScrollRunnable, delay);
-                }
-            }
-        } else {
-            clearScrollRunnable();
-        }
-    }
-
     /**
      * Call this from a drag source view.
      */
@@ -647,13 +607,6 @@
                 // Remember where the motion event started
                 mMotionDownX = dragLayerX;
                 mMotionDownY = dragLayerY;
-
-                if ((dragLayerX < mScrollZone) || (dragLayerX > mScrollView.getWidth() - mScrollZone)) {
-                    mScrollState = SCROLL_WAITING_IN_ZONE;
-                    mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
-                } else {
-                    mScrollState = SCROLL_OUTSIDE_ZONE;
-                }
                 break;
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
@@ -703,21 +656,29 @@
 
         ViewConfiguration config = ViewConfiguration.get(mLauncher);
         mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity());
-
+        PointF vel = new PointF(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
+        float theta = MAX_FLING_DEGREES + 1;
         if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) {
             // Do a quick dot product test to ensure that we are flinging upwards
-            PointF vel = new PointF(mVelocityTracker.getXVelocity(),
-                    mVelocityTracker.getYVelocity());
             PointF upVec = new PointF(0f, -1f);
-            float theta = (float) Math.acos(((vel.x * upVec.x) + (vel.y * upVec.y)) /
-                    (vel.length() * upVec.length()));
-            if (theta <= Math.toRadians(MAX_FLING_DEGREES)) {
-                return vel;
-            }
+            theta = getAngleBetweenVectors(vel, upVec);
+        } else if (mLauncher.getDeviceProfile().isVerticalBarLayout() &&
+                mVelocityTracker.getXVelocity() < mFlingToDeleteThresholdVelocity) {
+            // Remove icon is on left side instead of top, so check if we are flinging to the left.
+            PointF leftVec = new PointF(-1f, 0f);
+            theta = getAngleBetweenVectors(vel, leftVec);
+        }
+        if (theta <= Math.toRadians(MAX_FLING_DEGREES)) {
+            return vel;
         }
         return null;
     }
 
+    private float getAngleBetweenVectors(PointF vec1, PointF vec2) {
+        return (float) Math.acos(((vec1.x * vec2.x) + (vec1.y * vec2.y)) /
+                (vec1.length() * vec2.length()));
+    }
+
     void drop(DropTarget dropTarget, float x, float y, PointF flingVel) {
         final int[] coordinates = mCoordinatesTemp;
 
@@ -838,13 +799,6 @@
         }
     }
 
-    /**
-     * Set which view scrolls for touch events near the edge of the screen.
-     */
-    public void setScrollView(View v) {
-        mScrollView = v;
-    }
-
     private class ScrollRunnable implements Runnable {
         private int mDirection;
 
@@ -859,14 +813,7 @@
                     mDragScroller.scrollRight();
                 }
                 mScrollState = SCROLL_OUTSIDE_ZONE;
-                mDistanceSinceScroll = 0;
                 mDragScroller.onExitScrollArea();
-                mLauncher.getDragLayer().onExitScrollArea();
-
-                if (isDragging()) {
-                    // Check the scroll again so that we can requeue the scroller if necessary
-                    checkScrollState(mLastTouch[0], mLastTouch[1]);
-                }
             }
         }
 
diff --git a/src/com/android/launcher3/DragDriver.java b/src/com/android/launcher3/dragndrop/DragDriver.java
similarity index 97%
rename from src/com/android/launcher3/DragDriver.java
rename to src/com/android/launcher3/dragndrop/DragDriver.java
index 12545fb..6e4b430 100644
--- a/src/com/android/launcher3/DragDriver.java
+++ b/src/com/android/launcher3/dragndrop/DragDriver.java
@@ -14,7 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.launcher3;
+package com.android.launcher3.dragndrop;
+
+import com.android.launcher3.AnotherWindowDropTarget;
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Utilities;
 
 import android.content.ClipData;
 import android.content.Intent;
diff --git a/src/com/android/launcher3/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
similarity index 91%
rename from src/com/android/launcher3/DragLayer.java
rename to src/com/android/launcher3/dragndrop/DragLayer.java
index f523c49..adcc12c 100644
--- a/src/com/android/launcher3/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.launcher3;
+package com.android.launcher3.dragndrop;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -40,9 +40,24 @@
 import android.widget.FrameLayout;
 import android.widget.TextView;
 
+import com.android.launcher3.AppWidgetResizeFrame;
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.Folder;
+import com.android.launcher3.FolderIcon;
+import com.android.launcher3.InsettableFrameLayout;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetHostView;
+import com.android.launcher3.SearchDropTargetBar;
+import com.android.launcher3.ShortcutAndWidgetContainer;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.Workspace;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.util.Thunk;
 
+import com.android.launcher3.R;
+
 import java.util.ArrayList;
 
 /**
@@ -87,15 +102,6 @@
     // Darkening scrim
     private float mBackgroundAlpha = 0;
 
-    // Related to adjacent page hints
-    private final Rect mScrollChildPosition = new Rect();
-    private boolean mInScrollArea;
-    private boolean mShowPageHints;
-    private Drawable mLeftHoverDrawable;
-    private Drawable mRightHoverDrawable;
-    private Drawable mLeftHoverDrawableActive;
-    private Drawable mRightHoverDrawableActive;
-
     private boolean mBlockTouches = false;
 
     /**
@@ -111,12 +117,7 @@
         setMotionEventSplittingEnabled(false);
         setChildrenDrawingOrderEnabled(true);
 
-        final Resources res = getResources();
-        mLeftHoverDrawable = res.getDrawable(R.drawable.page_hover_left);
-        mRightHoverDrawable = res.getDrawable(R.drawable.page_hover_right);
-        mLeftHoverDrawableActive = res.getDrawable(R.drawable.page_hover_left_active);
-        mRightHoverDrawableActive = res.getDrawable(R.drawable.page_hover_right_active);
-        mIsRtl = Utilities.isRtl(res);
+        mIsRtl = Utilities.isRtl(getResources());
     }
 
     public void setup(Launcher launcher, DragController controller) {
@@ -164,6 +165,11 @@
         if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
             return true;
         }
+
+        getDescendantRectRelativeToSelf(mLauncher.getAppInfoDropTargetBar(), mHitRect);
+        if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
+            return true;
+        }
         return false;
     }
 
@@ -323,6 +329,7 @@
 
             if (isInAccessibleDrag()) {
                 childrenForAccessibility.add(mLauncher.getSearchDropTargetBar());
+                childrenForAccessibility.add(mLauncher.getAppInfoDropTargetBar());
             }
         } else {
             super.addChildrenForAccessibility(childrenForAccessibility);
@@ -880,29 +887,6 @@
         }
     }
 
-    void onEnterScrollArea(int direction) {
-        mInScrollArea = true;
-        invalidate();
-    }
-
-    void onExitScrollArea() {
-        mInScrollArea = false;
-        invalidate();
-    }
-
-    void showPageHints() {
-        mShowPageHints = true;
-        Workspace workspace = mLauncher.getWorkspace();
-        getDescendantRectRelativeToSelf(workspace.getChildAt(workspace.numCustomPages()),
-                mScrollChildPosition);
-        invalidate();
-    }
-
-    void hidePageHints() {
-        mShowPageHints = false;
-        invalidate();
-    }
-
     @Override
     protected void dispatchDraw(Canvas canvas) {
         // Draw the background below children.
@@ -914,41 +898,6 @@
         super.dispatchDraw(canvas);
     }
 
-    private void drawPageHints(Canvas canvas) {
-        if (mShowPageHints) {
-            Workspace workspace = mLauncher.getWorkspace();
-            int width = getMeasuredWidth();
-            int page = workspace.getNextPage();
-            CellLayout leftPage = (CellLayout) workspace.getChildAt(mIsRtl ? page + 1 : page - 1);
-            CellLayout rightPage = (CellLayout) workspace.getChildAt(mIsRtl ? page - 1 : page + 1);
-
-            if (leftPage != null && leftPage.isDragTarget()) {
-                Drawable left = mInScrollArea && leftPage.getIsDragOverlapping() ?
-                        mLeftHoverDrawableActive : mLeftHoverDrawable;
-                left.setBounds(0, mScrollChildPosition.top,
-                        left.getIntrinsicWidth(), mScrollChildPosition.bottom);
-                left.draw(canvas);
-            }
-            if (rightPage != null && rightPage.isDragTarget()) {
-                Drawable right = mInScrollArea && rightPage.getIsDragOverlapping() ?
-                        mRightHoverDrawableActive : mRightHoverDrawable;
-                right.setBounds(width - right.getIntrinsicWidth(),
-                        mScrollChildPosition.top, width, mScrollChildPosition.bottom);
-                right.draw(canvas);
-            }
-        }
-    }
-
-    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
-        boolean ret = super.drawChild(canvas, child, drawingTime);
-
-        // We want to draw the page hints above the workspace, but below the drag view.
-        if (child instanceof Workspace) {
-            drawPageHints(canvas);
-        }
-        return ret;
-    }
-
     public void setBackgroundAlpha(float alpha) {
         if (alpha != mBackgroundAlpha) {
             mBackgroundAlpha = alpha;
diff --git a/src/com/android/launcher3/DragScroller.java b/src/com/android/launcher3/dragndrop/DragScroller.java
similarity index 96%
rename from src/com/android/launcher3/DragScroller.java
rename to src/com/android/launcher3/dragndrop/DragScroller.java
index e261f15..165d0b1 100644
--- a/src/com/android/launcher3/DragScroller.java
+++ b/src/com/android/launcher3/dragndrop/DragScroller.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.launcher3;
+package com.android.launcher3.dragndrop;
 
 /**
  * Handles scrolling while dragging
diff --git a/src/com/android/launcher3/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
similarity index 98%
rename from src/com/android/launcher3/DragView.java
rename to src/com/android/launcher3/dragndrop/DragView.java
index 235cff0..88e11fa 100644
--- a/src/com/android/launcher3/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.launcher3;
+package com.android.launcher3.dragndrop;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -35,7 +35,14 @@
 import android.os.Build;
 import android.view.View;
 import android.view.animation.DecelerateInterpolator;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.util.Thunk;
+
+import com.android.launcher3.R;
+
 import java.util.Arrays;
 
 public class DragView extends View {
diff --git a/src/com/android/launcher3/SpringLoadedDragController.java b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
similarity index 89%
rename from src/com/android/launcher3/SpringLoadedDragController.java
rename to src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
index 45edaef..d7f41c9 100644
--- a/src/com/android/launcher3/SpringLoadedDragController.java
+++ b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
@@ -14,7 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.launcher3;
+package com.android.launcher3.dragndrop;
+
+import com.android.launcher3.Alarm;
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.OnAlarmListener;
+import com.android.launcher3.Workspace;
 
 public class SpringLoadedDragController implements OnAlarmListener {
     // how long the user must hover over a mini-screen before it unshrinks
diff --git a/src/com/android/launcher3/testing/LauncherExtension.java b/src/com/android/launcher3/testing/LauncherExtension.java
index b4d7459..c6d5ad1 100644
--- a/src/com/android/launcher3/testing/LauncherExtension.java
+++ b/src/com/android/launcher3/testing/LauncherExtension.java
@@ -5,11 +5,13 @@
 import android.animation.ObjectAnimator;
 import android.content.ComponentName;
 import android.content.Intent;
+import android.graphics.Color;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.view.Menu;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.FrameLayout;
 
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.InsettableFrameLayout;
@@ -210,13 +212,39 @@
         public void startVoice() {
         }
 
+        CustomContentCallbacks mCustomContentCallbacks = new CustomContentCallbacks() {
+
+            // Custom content is completely shown. {@code fromResume} indicates whether this was caused
+            // by a onResume or by scrolling otherwise.
+            public void onShow(boolean fromResume) {
+            }
+
+            // Custom content is completely hidden
+            public void onHide() {
+            }
+
+            // Custom content scroll progress changed. From 0 (not showing) to 1 (fully showing).
+            public void onScrollProgressChanged(float progress) {
+
+            }
+
+            // Indicates whether the user is allowed to scroll away from the custom content.
+            public boolean isScrollingAllowed() {
+                return true;
+            }
+
+        };
+
         @Override
         public boolean hasCustomContentToLeft() {
-            return false;
+            return true;
         }
 
         @Override
         public void populateCustomContentContainer() {
+            FrameLayout customContent = new FrameLayout(LauncherExtension.this);
+            customContent.setBackgroundColor(Color.GRAY);
+            addToCustomContentPage(customContent, mCustomContentCallbacks, "");
         }
 
         @Override
@@ -281,7 +309,7 @@
 
         @Override
         public boolean hasLauncherOverlay() {
-            return true;
+            return false;
         }
 
         @Override
diff --git a/src/com/android/launcher3/util/FlingAnimation.java b/src/com/android/launcher3/util/FlingAnimation.java
index 55c5d7d..da8bae7 100644
--- a/src/com/android/launcher3/util/FlingAnimation.java
+++ b/src/com/android/launcher3/util/FlingAnimation.java
@@ -7,9 +7,9 @@
 import android.graphics.Rect;
 import android.view.animation.DecelerateInterpolator;
 
-import com.android.launcher3.DragLayer;
-import com.android.launcher3.DragView;
 import com.android.launcher3.DropTarget.DragObject;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.dragndrop.DragView;
 
 public class FlingAnimation implements AnimatorUpdateListener {
 
@@ -51,7 +51,7 @@
         mFrom.top += yOffset;
         mFrom.bottom -= yOffset;
 
-        mDuration = initDuration();
+        mDuration = Math.abs(vel.y) > Math.abs(vel.x) ? initFlingUpDuration() : initFlingLeftDuration();
         mAnimationTimeFraction = ((float) mDuration) / (mDuration + DRAG_END_DELAY);
     }
 
@@ -62,7 +62,7 @@
      *   - Calculate a constant acceleration in x direction such that the object reaches
      *     {@link #mIconRect} in the given time.
      */
-    protected int initDuration() {
+    protected int initFlingUpDuration() {
         float sY = -mFrom.bottom;
 
         float d = mUY * mUY + 2 * sY * MAX_ACCELERATION;
@@ -83,6 +83,34 @@
         return (int) Math.round(t);
     }
 
+    /**
+     * The fling animation is based on the following system
+     *   - Apply a constant force in the x direction to causing the fling to decelerate.
+     *   - The animation runs for the time taken by the object to go out of the screen.
+     *   - Calculate a constant acceleration in y direction such that the object reaches
+     *     {@link #mIconRect} in the given time.
+     */
+    protected int initFlingLeftDuration() {
+        float sX = -mFrom.right;
+
+        float d = mUX * mUX + 2 * sX * MAX_ACCELERATION;
+        if (d >= 0) {
+            // sX can be reached under the MAX_ACCELERATION. Use MAX_ACCELERATION for x direction.
+            mAX = MAX_ACCELERATION;
+        } else {
+            // sX is not reachable, decrease the acceleration so that sX is almost reached.
+            d = 0;
+            mAX = mUX * mUX / (2 * -sX);
+        }
+        double t = (-mUX - Math.sqrt(d)) / mAX;
+
+        float sY = -mFrom.exactCenterY() + mIconRect.exactCenterY();
+
+        // Find vertical acceleration such that: u*t + a*t*t/2 = s
+        mAY = (float) ((sY - t * mUY) * 2 / (t * t));
+        return (int) Math.round(t);
+    }
+
     public final int getDuration() {
         return mDuration + DRAG_END_DELAY;
     }
diff --git a/src/com/android/launcher3/util/ParcelableSparseArray.java b/src/com/android/launcher3/util/ParcelableSparseArray.java
new file mode 100644
index 0000000..093577e
--- /dev/null
+++ b/src/com/android/launcher3/util/ParcelableSparseArray.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright (C) 2015 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.util;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.SparseArray;
+
+public class ParcelableSparseArray extends SparseArray<Parcelable> implements Parcelable {
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        final int count = size();
+        dest.writeInt(count);
+        for (int i = 0; i < count; i++) {
+            dest.writeInt(keyAt(i));
+            dest.writeParcelable(valueAt(i), 0);
+        }
+    }
+
+    public static final Parcelable.Creator<ParcelableSparseArray> CREATOR =
+            new Parcelable.Creator<ParcelableSparseArray>() {
+        public ParcelableSparseArray createFromParcel(Parcel source) {
+            final ParcelableSparseArray array = new ParcelableSparseArray();
+            final ClassLoader loader = array.getClass().getClassLoader();
+            final int count = source.readInt();
+            for (int i = 0; i < count; i++) {
+                array.put(source.readInt(), source.readParcelable(loader));
+            }
+            return array;
+        }
+
+        public ParcelableSparseArray[] newArray(int size) {
+            return new ParcelableSparseArray[size];
+        }
+    };
+}
diff --git a/src/com/android/launcher3/util/ViewOnDrawExecutor.java b/src/com/android/launcher3/util/ViewOnDrawExecutor.java
new file mode 100644
index 0000000..01808ba
--- /dev/null
+++ b/src/com/android/launcher3/util/ViewOnDrawExecutor.java
@@ -0,0 +1,96 @@
+/**
+ * Copyright (C) 2015 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.util;
+
+import android.view.View;
+import android.view.View.OnAttachStateChangeListener;
+import android.view.ViewTreeObserver.OnDrawListener;
+
+import com.android.launcher3.DeferredHandler;
+import com.android.launcher3.Launcher;
+
+import java.util.ArrayList;
+import java.util.concurrent.Executor;
+
+/**
+ * An executor which runs all the tasks after the first onDraw is called on the target view.
+ */
+public class ViewOnDrawExecutor implements Executor, OnDrawListener, Runnable,
+        OnAttachStateChangeListener {
+
+    private final ArrayList<Runnable> mTasks = new ArrayList<>();
+    private final DeferredHandler mHandler;
+
+    private Launcher mLauncher;
+    private View mAttachedView;
+    private boolean mCompleted;
+
+    public ViewOnDrawExecutor(DeferredHandler handler) {
+        mHandler = handler;
+    }
+
+    public void attachTo(Launcher launcher) {
+        mLauncher = launcher;
+        mAttachedView = launcher.getWorkspace();
+        mAttachedView.addOnAttachStateChangeListener(this);
+
+        attachObserver();
+    }
+
+    private void attachObserver() {
+        if (!mCompleted) {
+            mAttachedView.getViewTreeObserver().addOnDrawListener(this);
+        }
+    }
+
+    @Override
+    public void execute(Runnable command) {
+        mTasks.add(command);
+    }
+
+    @Override
+    public void onViewAttachedToWindow(View v) {
+        attachObserver();
+    }
+
+    @Override
+    public void onViewDetachedFromWindow(View v) { }
+
+    @Override
+    public void onDraw() {
+        mAttachedView.post(this);
+    }
+
+    @Override
+    public void run() {
+        for (final Runnable r : mTasks) {
+            mHandler.post(r);
+        }
+        markCompleted();
+    }
+
+    public void markCompleted() {
+        mTasks.clear();
+        if (mAttachedView != null) {
+            mAttachedView.getViewTreeObserver().removeOnDrawListener(this);
+            mAttachedView.removeOnAttachStateChangeListener(this);
+        }
+        if (mLauncher != null) {
+            mLauncher.clearPendingExecutor(this);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 94bbd92..70eceb9 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -36,6 +36,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.SimpleOnStylusPressListener;
 import com.android.launcher3.R;
 import com.android.launcher3.StylusEventHelper;
 import com.android.launcher3.WidgetPreviewLoader;
@@ -93,7 +94,7 @@
 
         final Resources r = context.getResources();
         mLauncher = (Launcher) context;
-        mStylusEventHelper = new StylusEventHelper(this);
+        mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
 
         mDimensionsFormatString = r.getString(R.string.widget_dims_format);
         setContainerWidth();
@@ -211,7 +212,7 @@
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
         boolean handled = super.onTouchEvent(ev);
-        if (mStylusEventHelper.checkAndPerformStylusEvent(ev)) {
+        if (mStylusEventHelper.onMotionEvent(ev)) {
             return true;
         }
         return handled;
diff --git a/src/com/android/launcher3/widget/WidgetHostViewLoader.java b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
index 1966f9f..7607d85 100644
--- a/src/com/android/launcher3/widget/WidgetHostViewLoader.java
+++ b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
@@ -10,14 +10,14 @@
 import android.view.View;
 
 import com.android.launcher3.AppWidgetResizeFrame;
-import com.android.launcher3.DragController.DragListener;
-import com.android.launcher3.DragLayer;
 import com.android.launcher3.DragSource;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
+import com.android.launcher3.dragndrop.DragController.DragListener;
+import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.util.Thunk;
 
 public class WidgetHostViewLoader implements DragListener {
diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java
index fd6d28b..81a8465 100644
--- a/src/com/android/launcher3/widget/WidgetsContainerView.java
+++ b/src/com/android/launcher3/widget/WidgetsContainerView.java
@@ -32,7 +32,6 @@
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeleteDropTarget;
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.DragController;
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.Folder;
@@ -45,6 +44,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.WidgetPreviewLoader;
 import com.android.launcher3.Workspace;
+import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.util.Thunk;
 
@@ -90,7 +90,7 @@
         super(context, attrs, defStyleAttr);
         mLauncher = (Launcher) context;
         mDragController = mLauncher.getDragController();
-        mAdapter = new WidgetsListAdapter(context, this, this, mLauncher);
+        mAdapter = new WidgetsListAdapter(this, this, mLauncher);
         mIconCache = (LauncherAppState.getInstance()).getIconCache();
         if (DEBUG) {
             Log.d(TAG, "WidgetsContainerView constructor");
@@ -253,9 +253,11 @@
 
         // Start the drag
         mLauncher.lockScreenOrientation();
-        mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, preview, clipAlpha);
         mDragController.startDrag(image, preview, this, createItemInfo,
                 bounds, DragController.DRAG_ACTION_COPY, scale);
+        // This call expects the extra empty screen to already be created, which is why we call it
+        // after mDragController.startDrag().
+        mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, preview, clipAlpha);
 
         preview.recycle();
         return true;
diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java
index 885d96f..ac9d62e 100644
--- a/src/com/android/launcher3/widget/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java
@@ -64,20 +64,17 @@
     private View.OnClickListener mIconClickListener;
     private View.OnLongClickListener mIconLongClickListener;
 
-    private static final int PRESET_INDENT_SIZE_TABLET = 56;
-    private int mIndent = 0;
+    private final int mIndent;
 
-    public WidgetsListAdapter(Context context,
-            View.OnClickListener iconClickListener,
+    public WidgetsListAdapter(View.OnClickListener iconClickListener,
             View.OnLongClickListener iconLongClickListener,
             Launcher launcher) {
-        mLayoutInflater = LayoutInflater.from(context);
+        mLayoutInflater = launcher.getLayoutInflater();
 
         mIconClickListener = iconClickListener;
         mIconLongClickListener = iconLongClickListener;
         mLauncher = launcher;
-
-        setContainerHeight();
+        mIndent = launcher.getResources().getDimensionPixelSize(R.dimen.widget_section_indent);
     }
 
     public void setWidgetsModel(WidgetsModel w) {
@@ -206,12 +203,4 @@
         }
         return mWidgetPreviewLoader;
     }
-
-    private void setContainerHeight() {
-        Resources r = mLauncher.getResources();
-        DeviceProfile profile = mLauncher.getDeviceProfile();
-        if (profile.isLargeTablet || profile.isTablet) {
-            mIndent = Utilities.pxFromDp(PRESET_INDENT_SIZE_TABLET, r.getDisplayMetrics());
-        }
-    }
 }
diff --git a/src/com/android/launcher3/widget/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
index 61e63cd..884bdc4 100644
--- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
@@ -64,10 +64,6 @@
         return Color.WHITE;
     }
 
-    public int getFastScrollerThumbInactiveColor(int defaultInactiveThumbColor) {
-        return getResources().getColor(R.color.widgets_view_fastscroll_thumb_inactive_color);
-    }
-
     /**
      * Sets the widget model in this view, used to determine the fast scroll position.
      */
@@ -92,6 +88,12 @@
      */
     @Override
     public String scrollToPositionAtProgress(float touchFraction) {
+        // Skip early if widgets are not bound.
+        if (mWidgets == null) {
+            return "";
+        }
+
+        // Skip early if there are no widgets.
         int rowCount = mWidgets.getPackageSize();
         if (rowCount == 0) {
             return "";
@@ -102,7 +104,7 @@
 
         getCurScrollState(mScrollPosState);
         float pos = rowCount * touchFraction;
-        int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight, 0);
+        int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight);
         LinearLayoutManager layoutManager = ((LinearLayoutManager) getLayoutManager());
         layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction));
 
@@ -115,36 +117,44 @@
      * Updates the bounds for the scrollbar.
      */
     @Override
-    public void onUpdateScrollbar() {
-        int rowCount = mWidgets.getPackageSize();
+    public void onUpdateScrollbar(int dy) {
+        // Skip early if widgets are not bound.
+        if (mWidgets == null) {
+            return;
+        }
 
-        // Skip early if, there are no items.
+        // Skip early if there are no widgets.
+        int rowCount = mWidgets.getPackageSize();
         if (rowCount == 0) {
-            mScrollbar.setScrollbarThumbOffset(-1, -1);
+            mScrollbar.setThumbOffset(-1, -1);
             return;
         }
 
         // Skip early if, there no child laid out in the container.
         getCurScrollState(mScrollPosState);
         if (mScrollPosState.rowIndex < 0) {
-            mScrollbar.setScrollbarThumbOffset(-1, -1);
+            mScrollbar.setThumbOffset(-1, -1);
             return;
         }
 
-        synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount, 0);
+        synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount);
     }
 
     /**
      * Returns the current scroll state.
      */
-    private void getCurScrollState(ScrollPositionState stateOut) {
+    protected void getCurScrollState(ScrollPositionState stateOut) {
         stateOut.rowIndex = -1;
         stateOut.rowTopOffset = -1;
         stateOut.rowHeight = -1;
 
-        int rowCount = mWidgets.getPackageSize();
+        // Skip early if widgets are not bound.
+        if (mWidgets == null) {
+            return;
+        }
 
         // Return early if there are no items
+        int rowCount = mWidgets.getPackageSize();
         if (rowCount == 0) {
             return;
         }