Merge commit '5c6bdbbc27525acc8c49cea6a1de36723a13fac7' into manual_merge_5c6bdbbc

Change-Id: I508bce36edca28ed565338e5173b64eba14e89d4
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 99a6ad9..9014234 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -51,10 +51,8 @@
     <uses-permission android:name="android.permission.CALL_PHONE" />
     <uses-permission android:name="android.permission.SET_WALLPAPER" />
     <uses-permission android:name="android.permission.SET_WALLPAPER_HINTS" />
-    <uses-permission android:name="android.permission.VIBRATE" />
     <uses-permission android:name="android.permission.BIND_APPWIDGET" />
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.BROADCAST_STICKY"/>
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
     <uses-permission android:name="com.android.launcher.permission.READ_SETTINGS" />
     <uses-permission android:name="com.android.launcher.permission.WRITE_SETTINGS" />
@@ -180,13 +178,6 @@
             >
         </service>
 
-        <receiver
-            android:name="com.android.launcher3.WallpaperChangedReceiver">
-            <intent-filter>
-                <action android:name="android.intent.action.WALLPAPER_CHANGED" />
-            </intent-filter>
-        </receiver>
-
         <!-- Intent received used to install shortcuts from other applications -->
         <receiver
             android:name="com.android.launcher3.InstallShortcutReceiver"
@@ -203,12 +194,6 @@
             </intent-filter>
         </receiver>
 
-        <receiver android:name="com.android.launcher3.StartupReceiver" >
-            <intent-filter>
-                <action android:name="android.intent.action.BOOT_COMPLETED" />
-            </intent-filter>
-        </receiver>
-
         <!-- The settings provider contains Home's data, like the workspace favorites -->
         <provider
             android:name="com.android.launcher3.LauncherProvider"
diff --git a/WallpaperPicker/res/anim/fade_out.xml b/WallpaperPicker/res/anim/fade_out.xml
new file mode 100644
index 0000000..7737cf5
--- /dev/null
+++ b/WallpaperPicker/res/anim/fade_out.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/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/common/BitmapUtils.java b/WallpaperPicker/src/com/android/gallery3d/common/BitmapUtils.java
index 9ac5c1b..6ddda87 100644
--- a/WallpaperPicker/src/com/android/gallery3d/common/BitmapUtils.java
+++ b/WallpaperPicker/src/com/android/gallery3d/common/BitmapUtils.java
@@ -21,8 +21,6 @@
 import android.net.Uri;
 import android.util.Log;
 
-import com.android.gallery3d.exif.ExifInterface;
-
 import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -42,38 +40,26 @@
     }
 
     public static int getRotationFromExif(Context context, Uri uri) {
-        return BitmapUtils.getRotationFromExifHelper(null, 0, context, uri);
+        return BitmapUtils.getRotationFromExifHelper(null, 0, uri, context);
     }
 
-    public static int getRotationFromExif(Resources res, int resId) {
-        return BitmapUtils.getRotationFromExifHelper(res, resId, null, null);
+    public static int getRotationFromExif(Resources res, int resId, Context context) {
+        return BitmapUtils.getRotationFromExifHelper(res, resId, null, context);
     }
 
-    private static int getRotationFromExifHelper(Resources res, int resId, Context context, Uri uri) {
-        ExifInterface ei = new ExifInterface();
+    private static int getRotationFromExifHelper(Resources res, int resId,
+            Uri uri, Context context) {
         InputStream is = null;
-        BufferedInputStream bis = null;
         try {
             if (uri != null) {
                 is = context.getContentResolver().openInputStream(uri);
-                bis = new BufferedInputStream(is);
-                ei.readExif(bis);
             } else {
                 is = res.openRawResource(resId);
-                bis = new BufferedInputStream(is);
-                ei.readExif(bis);
             }
-            Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION);
-            if (ori != null) {
-                return ExifInterface.getRotationForOrientationValue(ori.shortValue());
-            }
-        } catch (IOException e) {
-            Log.w(TAG, "Getting exif data failed", e);
-        } catch (NullPointerException e) {
-            // Sometimes the ExifInterface has an internal NPE if Exif data isn't valid
+            return ExifOrientation.readRotation(new BufferedInputStream(is), context);
+        } catch (IOException | NullPointerException e) {
             Log.w(TAG, "Getting exif data failed", e);
         } finally {
-            Utils.closeSilently(bis);
             Utils.closeSilently(is);
         }
         return 0;
diff --git a/WallpaperPicker/src/com/android/gallery3d/common/ExifOrientation.java b/WallpaperPicker/src/com/android/gallery3d/common/ExifOrientation.java
new file mode 100644
index 0000000..ad4370c
--- /dev/null
+++ b/WallpaperPicker/src/com/android/gallery3d/common/ExifOrientation.java
@@ -0,0 +1,145 @@
+/**
+ * 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.gallery3d.common;
+
+import android.content.Context;
+import android.media.ExifInterface;
+import android.util.Log;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ExifOrientation {
+    private static final String TAG = "ExifOrientation";
+    private static final boolean DEBUG = false;
+
+    private static final short SOI =  (short) 0xFFD8;   // start of input
+    private static final short APP0 = (short) 0xFFE0;
+    private static final short APPF = (short) 0xFFEF;
+    private static final short APP1 = (short) 0xFFE1;
+    private static final short SOS = (short) 0xFFDA;    // start of stream
+    private static final short EOI = (short) 0xFFD9;    // end of input
+
+    // The header is available in first 64 bytes, so reading upto 128 bytes
+    // should be more than enough.
+    private static final int MAX_BYTES_TO_READ = 128 * 1024;
+
+    /**
+     * Parses the rotation of the JPEG image from the input stream.
+     */
+    public static final int readRotation(InputStream in, Context context) {
+        // Since the platform implementation only takes file input, create a temporary file
+        // with just the image header.
+        File tempFile = null;
+        DataOutputStream tempOut = null;
+
+        try {
+        DataInputStream din = new DataInputStream(in);
+            int pos = 0;
+            if (din.readShort() == SOI) {
+                pos += 2;
+
+                short marker = din.readShort();
+                pos += 2;
+
+                while ((marker >= APP0 && marker <= APPF) && pos < MAX_BYTES_TO_READ) {
+                    int length = din.readUnsignedShort();
+                    if (length < 2) {
+                        throw new IOException("Invalid header size");
+                    }
+
+                    // We only want APP1 headers
+                    if (length > 2) {
+                        if (marker == APP1) {
+                            // Copy the header
+                            if (tempFile == null) {
+                                tempFile = File.createTempFile(TAG, ".jpg", context.getCacheDir());
+                                tempOut = new DataOutputStream(new FileOutputStream(tempFile));
+                                tempOut.writeShort(SOI);
+                            }
+
+                            tempOut.writeShort(marker);
+                            tempOut.writeShort(length);
+
+                            byte[] header = new byte[length - 2];
+                            din.read(header);
+                            tempOut.write(header);
+                        } else {
+                            din.skip(length - 2);
+                        }
+                    }
+                    pos += length;
+
+                    marker = din.readShort();
+                    pos += 2;
+                }
+
+                if (tempOut != null) {
+                    // Write empty image data.
+                    tempOut.writeShort(SOS);
+                    // Write the frame size as 2. Since this includes the size bytes as well
+                    // (short = 2 bytes), it implies there is 0 byte of image data.
+                    tempOut.writeShort(2);
+
+                    // End of input
+                    tempOut.writeShort(EOI);
+                    tempOut.close();
+
+                    return readRotation(tempFile.getAbsolutePath());
+                }
+            }
+        } catch (IOException e) {
+            if (DEBUG) {
+                Log.d(TAG, "Error parsing input stream", e);
+            }
+        } finally {
+            Utils.closeSilently(in);
+            Utils.closeSilently(tempOut);
+            if (tempFile != null) {
+                tempFile.delete();
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * Parses the rotation of the JPEG image.
+     */
+    public static final int readRotation(String filePath) {
+        try {
+            ExifInterface exif = new ExifInterface(filePath);
+            switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0)) {
+                case ExifInterface.ORIENTATION_ROTATE_90:
+                    return 90;
+                case ExifInterface.ORIENTATION_ROTATE_270:
+                    return 270;
+                case ExifInterface.ORIENTATION_ROTATE_180:
+                    return 180;
+                default:
+                    return 0;
+            }
+        } catch (IOException e) {
+            if (DEBUG) {
+                Log.d(TAG, "Error reading file", e);
+            }
+        }
+        return 0;
+    }
+}
diff --git a/WallpaperPicker/src/com/android/gallery3d/exif/ByteBufferInputStream.java b/WallpaperPicker/src/com/android/gallery3d/exif/ByteBufferInputStream.java
deleted file mode 100644
index 7fb9f22..0000000
--- a/WallpaperPicker/src/com/android/gallery3d/exif/ByteBufferInputStream.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2012 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.exif;
-
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-
-class ByteBufferInputStream extends InputStream {
-
-    private ByteBuffer mBuf;
-
-    public ByteBufferInputStream(ByteBuffer buf) {
-        mBuf = buf;
-    }
-
-    @Override
-    public int read() {
-        if (!mBuf.hasRemaining()) {
-            return -1;
-        }
-        return mBuf.get() & 0xFF;
-    }
-
-    @Override
-    public int read(byte[] bytes, int off, int len) {
-        if (!mBuf.hasRemaining()) {
-            return -1;
-        }
-
-        len = Math.min(len, mBuf.remaining());
-        mBuf.get(bytes, off, len);
-        return len;
-    }
-}
diff --git a/WallpaperPicker/src/com/android/gallery3d/exif/CountedDataInputStream.java b/WallpaperPicker/src/com/android/gallery3d/exif/CountedDataInputStream.java
deleted file mode 100644
index dfd4a1a..0000000
--- a/WallpaperPicker/src/com/android/gallery3d/exif/CountedDataInputStream.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2012 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.exif;
-
-import java.io.EOFException;
-import java.io.FilterInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.charset.Charset;
-
-class CountedDataInputStream extends FilterInputStream {
-
-    private int mCount = 0;
-
-    // allocate a byte buffer for a long value;
-    private final byte mByteArray[] = new byte[8];
-    private final ByteBuffer mByteBuffer = ByteBuffer.wrap(mByteArray);
-
-    protected CountedDataInputStream(InputStream in) {
-        super(in);
-    }
-
-    public int getReadByteCount() {
-        return mCount;
-    }
-
-    @Override
-    public int read(byte[] b) throws IOException {
-        int r = in.read(b);
-        mCount += (r >= 0) ? r : 0;
-        return r;
-    }
-
-    @Override
-    public int read(byte[] b, int off, int len) throws IOException {
-        int r = in.read(b, off, len);
-        mCount += (r >= 0) ? r : 0;
-        return r;
-    }
-
-    @Override
-    public int read() throws IOException {
-        int r = in.read();
-        mCount += (r >= 0) ? 1 : 0;
-        return r;
-    }
-
-    @Override
-    public long skip(long length) throws IOException {
-        long skip = in.skip(length);
-        mCount += skip;
-        return skip;
-    }
-
-    public void skipOrThrow(long length) throws IOException {
-        if (skip(length) != length) throw new EOFException();
-    }
-
-    public void skipTo(long target) throws IOException {
-        long cur = mCount;
-        long diff = target - cur;
-        assert(diff >= 0);
-        skipOrThrow(diff);
-    }
-
-    public void readOrThrow(byte[] b, int off, int len) throws IOException {
-        int r = read(b, off, len);
-        if (r != len) throw new EOFException();
-    }
-
-    public void readOrThrow(byte[] b) throws IOException {
-        readOrThrow(b, 0, b.length);
-    }
-
-    public void setByteOrder(ByteOrder order) {
-        mByteBuffer.order(order);
-    }
-
-    public ByteOrder getByteOrder() {
-        return mByteBuffer.order();
-    }
-
-    public short readShort() throws IOException {
-        readOrThrow(mByteArray, 0 ,2);
-        mByteBuffer.rewind();
-        return mByteBuffer.getShort();
-    }
-
-    public int readUnsignedShort() throws IOException {
-        return readShort() & 0xffff;
-    }
-
-    public int readInt() throws IOException {
-        readOrThrow(mByteArray, 0 , 4);
-        mByteBuffer.rewind();
-        return mByteBuffer.getInt();
-    }
-
-    public long readUnsignedInt() throws IOException {
-        return readInt() & 0xffffffffL;
-    }
-
-    public long readLong() throws IOException {
-        readOrThrow(mByteArray, 0 , 8);
-        mByteBuffer.rewind();
-        return mByteBuffer.getLong();
-    }
-
-    public String readString(int n) throws IOException {
-        byte buf[] = new byte[n];
-        readOrThrow(buf);
-        return new String(buf, "UTF8");
-    }
-
-    public String readString(int n, Charset charset) throws IOException {
-        byte buf[] = new byte[n];
-        readOrThrow(buf);
-        return new String(buf, charset);
-    }
-}
\ No newline at end of file
diff --git a/WallpaperPicker/src/com/android/gallery3d/exif/ExifData.java b/WallpaperPicker/src/com/android/gallery3d/exif/ExifData.java
deleted file mode 100644
index 8422382..0000000
--- a/WallpaperPicker/src/com/android/gallery3d/exif/ExifData.java
+++ /dev/null
@@ -1,348 +0,0 @@
-/*
- * Copyright (C) 2012 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.exif;
-
-import android.util.Log;
-
-import java.io.UnsupportedEncodingException;
-import java.nio.ByteOrder;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * This class stores the EXIF header in IFDs according to the JPEG
- * specification. It is the result produced by {@link ExifReader}.
- *
- * @see ExifReader
- * @see IfdData
- */
-class ExifData {
-    private static final String TAG = "ExifData";
-    private static final byte[] USER_COMMENT_ASCII = {
-            0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00
-    };
-    private static final byte[] USER_COMMENT_JIS = {
-            0x4A, 0x49, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00
-    };
-    private static final byte[] USER_COMMENT_UNICODE = {
-            0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0x00
-    };
-
-    private final IfdData[] mIfdDatas = new IfdData[IfdId.TYPE_IFD_COUNT];
-    private byte[] mThumbnail;
-    private ArrayList<byte[]> mStripBytes = new ArrayList<byte[]>();
-    private final ByteOrder mByteOrder;
-
-    ExifData(ByteOrder order) {
-        mByteOrder = order;
-    }
-
-    /**
-     * Gets the compressed thumbnail. Returns null if there is no compressed
-     * thumbnail.
-     *
-     * @see #hasCompressedThumbnail()
-     */
-    protected byte[] getCompressedThumbnail() {
-        return mThumbnail;
-    }
-
-    /**
-     * Sets the compressed thumbnail.
-     */
-    protected void setCompressedThumbnail(byte[] thumbnail) {
-        mThumbnail = thumbnail;
-    }
-
-    /**
-     * Returns true it this header contains a compressed thumbnail.
-     */
-    protected boolean hasCompressedThumbnail() {
-        return mThumbnail != null;
-    }
-
-    /**
-     * Adds an uncompressed strip.
-     */
-    protected void setStripBytes(int index, byte[] strip) {
-        if (index < mStripBytes.size()) {
-            mStripBytes.set(index, strip);
-        } else {
-            for (int i = mStripBytes.size(); i < index; i++) {
-                mStripBytes.add(null);
-            }
-            mStripBytes.add(strip);
-        }
-    }
-
-    /**
-     * Gets the strip count.
-     */
-    protected int getStripCount() {
-        return mStripBytes.size();
-    }
-
-    /**
-     * Gets the strip at the specified index.
-     *
-     * @exceptions #IndexOutOfBoundException
-     */
-    protected byte[] getStrip(int index) {
-        return mStripBytes.get(index);
-    }
-
-    /**
-     * Returns true if this header contains uncompressed strip.
-     */
-    protected boolean hasUncompressedStrip() {
-        return mStripBytes.size() != 0;
-    }
-
-    /**
-     * Gets the byte order.
-     */
-    protected ByteOrder getByteOrder() {
-        return mByteOrder;
-    }
-
-    /**
-     * Returns the {@link IfdData} object corresponding to a given IFD if it
-     * exists or null.
-     */
-    protected IfdData getIfdData(int ifdId) {
-        if (ExifTag.isValidIfd(ifdId)) {
-            return mIfdDatas[ifdId];
-        }
-        return null;
-    }
-
-    /**
-     * Adds IFD data. If IFD data of the same type already exists, it will be
-     * replaced by the new data.
-     */
-    protected void addIfdData(IfdData data) {
-        mIfdDatas[data.getId()] = data;
-    }
-
-    /**
-     * Returns the {@link IfdData} object corresponding to a given IFD or
-     * generates one if none exist.
-     */
-    protected IfdData getOrCreateIfdData(int ifdId) {
-        IfdData ifdData = mIfdDatas[ifdId];
-        if (ifdData == null) {
-            ifdData = new IfdData(ifdId);
-            mIfdDatas[ifdId] = ifdData;
-        }
-        return ifdData;
-    }
-
-    /**
-     * Returns the tag with a given TID in the given IFD if the tag exists.
-     * Otherwise returns null.
-     */
-    protected ExifTag getTag(short tag, int ifd) {
-        IfdData ifdData = mIfdDatas[ifd];
-        return (ifdData == null) ? null : ifdData.getTag(tag);
-    }
-
-    /**
-     * Adds the given ExifTag to its default IFD and returns an existing ExifTag
-     * with the same TID or null if none exist.
-     */
-    protected ExifTag addTag(ExifTag tag) {
-        if (tag != null) {
-            int ifd = tag.getIfd();
-            return addTag(tag, ifd);
-        }
-        return null;
-    }
-
-    /**
-     * Adds the given ExifTag to the given IFD and returns an existing ExifTag
-     * with the same TID or null if none exist.
-     */
-    protected ExifTag addTag(ExifTag tag, int ifdId) {
-        if (tag != null && ExifTag.isValidIfd(ifdId)) {
-            IfdData ifdData = getOrCreateIfdData(ifdId);
-            return ifdData.setTag(tag);
-        }
-        return null;
-    }
-
-    protected void clearThumbnailAndStrips() {
-        mThumbnail = null;
-        mStripBytes.clear();
-    }
-
-    /**
-     * Removes the thumbnail and its related tags. IFD1 will be removed.
-     */
-    protected void removeThumbnailData() {
-        clearThumbnailAndStrips();
-        mIfdDatas[IfdId.TYPE_IFD_1] = null;
-    }
-
-    /**
-     * Removes the tag with a given TID and IFD.
-     */
-    protected void removeTag(short tagId, int ifdId) {
-        IfdData ifdData = mIfdDatas[ifdId];
-        if (ifdData == null) {
-            return;
-        }
-        ifdData.removeTag(tagId);
-    }
-
-    /**
-     * Decodes the user comment tag into string as specified in the EXIF
-     * standard. Returns null if decoding failed.
-     */
-    protected String getUserComment() {
-        IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_0];
-        if (ifdData == null) {
-            return null;
-        }
-        ExifTag tag = ifdData.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_USER_COMMENT));
-        if (tag == null) {
-            return null;
-        }
-        if (tag.getComponentCount() < 8) {
-            return null;
-        }
-
-        byte[] buf = new byte[tag.getComponentCount()];
-        tag.getBytes(buf);
-
-        byte[] code = new byte[8];
-        System.arraycopy(buf, 0, code, 0, 8);
-
-        try {
-            if (Arrays.equals(code, USER_COMMENT_ASCII)) {
-                return new String(buf, 8, buf.length - 8, "US-ASCII");
-            } else if (Arrays.equals(code, USER_COMMENT_JIS)) {
-                return new String(buf, 8, buf.length - 8, "EUC-JP");
-            } else if (Arrays.equals(code, USER_COMMENT_UNICODE)) {
-                return new String(buf, 8, buf.length - 8, "UTF-16");
-            } else {
-                return null;
-            }
-        } catch (UnsupportedEncodingException e) {
-            Log.w(TAG, "Failed to decode the user comment");
-            return null;
-        }
-    }
-
-    /**
-     * Returns a list of all {@link ExifTag}s in the ExifData or null if there
-     * are none.
-     */
-    protected List<ExifTag> getAllTags() {
-        ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
-        for (IfdData d : mIfdDatas) {
-            if (d != null) {
-                ExifTag[] tags = d.getAllTags();
-                if (tags != null) {
-                    for (ExifTag t : tags) {
-                        ret.add(t);
-                    }
-                }
-            }
-        }
-        if (ret.size() == 0) {
-            return null;
-        }
-        return ret;
-    }
-
-    /**
-     * Returns a list of all {@link ExifTag}s in a given IFD or null if there
-     * are none.
-     */
-    protected List<ExifTag> getAllTagsForIfd(int ifd) {
-        IfdData d = mIfdDatas[ifd];
-        if (d == null) {
-            return null;
-        }
-        ExifTag[] tags = d.getAllTags();
-        if (tags == null) {
-            return null;
-        }
-        ArrayList<ExifTag> ret = new ArrayList<ExifTag>(tags.length);
-        for (ExifTag t : tags) {
-            ret.add(t);
-        }
-        if (ret.size() == 0) {
-            return null;
-        }
-        return ret;
-    }
-
-    /**
-     * Returns a list of all {@link ExifTag}s with a given TID or null if there
-     * are none.
-     */
-    protected List<ExifTag> getAllTagsForTagId(short tag) {
-        ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
-        for (IfdData d : mIfdDatas) {
-            if (d != null) {
-                ExifTag t = d.getTag(tag);
-                if (t != null) {
-                    ret.add(t);
-                }
-            }
-        }
-        if (ret.size() == 0) {
-            return null;
-        }
-        return ret;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj == null) {
-            return false;
-        }
-        if (obj instanceof ExifData) {
-            ExifData data = (ExifData) obj;
-            if (data.mByteOrder != mByteOrder ||
-                    data.mStripBytes.size() != mStripBytes.size() ||
-                    !Arrays.equals(data.mThumbnail, mThumbnail)) {
-                return false;
-            }
-            for (int i = 0; i < mStripBytes.size(); i++) {
-                if (!Arrays.equals(data.mStripBytes.get(i), mStripBytes.get(i))) {
-                    return false;
-                }
-            }
-            for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
-                IfdData ifd1 = data.getIfdData(i);
-                IfdData ifd2 = getIfdData(i);
-                if (ifd1 != ifd2 && ifd1 != null && !ifd1.equals(ifd2)) {
-                    return false;
-                }
-            }
-            return true;
-        }
-        return false;
-    }
-
-}
diff --git a/WallpaperPicker/src/com/android/gallery3d/exif/ExifInterface.java b/WallpaperPicker/src/com/android/gallery3d/exif/ExifInterface.java
deleted file mode 100644
index 9247e87..0000000
--- a/WallpaperPicker/src/com/android/gallery3d/exif/ExifInterface.java
+++ /dev/null
@@ -1,2407 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.gallery3d.exif;
-
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.util.SparseIntArray;
-
-import java.io.BufferedInputStream;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.Closeable;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.RandomAccessFile;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.channels.FileChannel.MapMode;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.TimeZone;
-
-/**
- * This class provides methods and constants for reading and writing jpeg file
- * metadata. It contains a collection of ExifTags, and a collection of
- * definitions for creating valid ExifTags. The collection of ExifTags can be
- * updated by: reading new ones from a file, deleting or adding existing ones,
- * or building new ExifTags from a tag definition. These ExifTags can be written
- * to a valid jpeg image as exif metadata.
- * <p>
- * Each ExifTag has a tag ID (TID) and is stored in a specific image file
- * directory (IFD) as specified by the exif standard. A tag definition can be
- * looked up with a constant that is a combination of TID and IFD. This
- * definition has information about the type, number of components, and valid
- * IFDs for a tag.
- *
- * @see ExifTag
- */
-public class ExifInterface {
-    public static final int TAG_NULL = -1;
-    public static final int IFD_NULL = -1;
-    public static final int DEFINITION_NULL = 0;
-
-    /**
-     * Tag constants for Jeita EXIF 2.2
-     */
-
-    // IFD 0
-    public static final int TAG_IMAGE_WIDTH =
-        defineTag(IfdId.TYPE_IFD_0, (short) 0x0100);
-    public static final int TAG_IMAGE_LENGTH =
-        defineTag(IfdId.TYPE_IFD_0, (short) 0x0101); // Image height
-    public static final int TAG_BITS_PER_SAMPLE =
-        defineTag(IfdId.TYPE_IFD_0, (short) 0x0102);
-    public static final int TAG_COMPRESSION =
-        defineTag(IfdId.TYPE_IFD_0, (short) 0x0103);
-    public static final int TAG_PHOTOMETRIC_INTERPRETATION =
-        defineTag(IfdId.TYPE_IFD_0, (short) 0x0106);
-    public static final int TAG_IMAGE_DESCRIPTION =
-        defineTag(IfdId.TYPE_IFD_0, (short) 0x010E);
-    public static final int TAG_MAKE =
-        defineTag(IfdId.TYPE_IFD_0, (short) 0x010F);
-    public static final int TAG_MODEL =
-        defineTag(IfdId.TYPE_IFD_0, (short) 0x0110);
-    public static final int TAG_STRIP_OFFSETS =
-        defineTag(IfdId.TYPE_IFD_0, (short) 0x0111);
-    public static final int TAG_ORIENTATION =
-        defineTag(IfdId.TYPE_IFD_0, (short) 0x0112);
-    public static final int TAG_SAMPLES_PER_PIXEL =
-        defineTag(IfdId.TYPE_IFD_0, (short) 0x0115);
-    public static final int TAG_ROWS_PER_STRIP =
-        defineTag(IfdId.TYPE_IFD_0, (short) 0x0116);
-    public static final int TAG_STRIP_BYTE_COUNTS =
-        defineTag(IfdId.TYPE_IFD_0, (short) 0x0117);
-    public static final int TAG_X_RESOLUTION =
-        defineTag(IfdId.TYPE_IFD_0, (short) 0x011A);
-    public static final int TAG_Y_RESOLUTION =
-        defineTag(IfdId.TYPE_IFD_0, (short) 0x011B);
-    public static final int TAG_PLANAR_CONFIGURATION =
-        defineTag(IfdId.TYPE_IFD_0, (short) 0x011C);
-    public static final int TAG_RESOLUTION_UNIT =
-        defineTag(IfdId.TYPE_IFD_0, (short) 0x0128);
-    public static final int TAG_TRANSFER_FUNCTION =
-        defineTag(IfdId.TYPE_IFD_0, (short) 0x012D);
-    public static final int TAG_SOFTWARE =
-        defineTag(IfdId.TYPE_IFD_0, (short) 0x0131);
-    public static final int TAG_DATE_TIME =
-        defineTag(IfdId.TYPE_IFD_0, (short) 0x0132);
-    public static final int TAG_ARTIST =
-        defineTag(IfdId.TYPE_IFD_0, (short) 0x013B);
-    public static final int TAG_WHITE_POINT =
-        defineTag(IfdId.TYPE_IFD_0, (short) 0x013E);
-    public static final int TAG_PRIMARY_CHROMATICITIES =
-        defineTag(IfdId.TYPE_IFD_0, (short) 0x013F);
-    public static final int TAG_Y_CB_CR_COEFFICIENTS =
-        defineTag(IfdId.TYPE_IFD_0, (short) 0x0211);
-    public static final int TAG_Y_CB_CR_SUB_SAMPLING =
-        defineTag(IfdId.TYPE_IFD_0, (short) 0x0212);
-    public static final int TAG_Y_CB_CR_POSITIONING =
-        defineTag(IfdId.TYPE_IFD_0, (short) 0x0213);
-    public static final int TAG_REFERENCE_BLACK_WHITE =
-        defineTag(IfdId.TYPE_IFD_0, (short) 0x0214);
-    public static final int TAG_COPYRIGHT =
-        defineTag(IfdId.TYPE_IFD_0, (short) 0x8298);
-    public static final int TAG_EXIF_IFD =
-        defineTag(IfdId.TYPE_IFD_0, (short) 0x8769);
-    public static final int TAG_GPS_IFD =
-        defineTag(IfdId.TYPE_IFD_0, (short) 0x8825);
-    // IFD 1
-    public static final int TAG_JPEG_INTERCHANGE_FORMAT =
-        defineTag(IfdId.TYPE_IFD_1, (short) 0x0201);
-    public static final int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH =
-        defineTag(IfdId.TYPE_IFD_1, (short) 0x0202);
-    // IFD Exif Tags
-    public static final int TAG_EXPOSURE_TIME =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x829A);
-    public static final int TAG_F_NUMBER =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x829D);
-    public static final int TAG_EXPOSURE_PROGRAM =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8822);
-    public static final int TAG_SPECTRAL_SENSITIVITY =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8824);
-    public static final int TAG_ISO_SPEED_RATINGS =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8827);
-    public static final int TAG_OECF =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8828);
-    public static final int TAG_EXIF_VERSION =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9000);
-    public static final int TAG_DATE_TIME_ORIGINAL =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9003);
-    public static final int TAG_DATE_TIME_DIGITIZED =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9004);
-    public static final int TAG_COMPONENTS_CONFIGURATION =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9101);
-    public static final int TAG_COMPRESSED_BITS_PER_PIXEL =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9102);
-    public static final int TAG_SHUTTER_SPEED_VALUE =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9201);
-    public static final int TAG_APERTURE_VALUE =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9202);
-    public static final int TAG_BRIGHTNESS_VALUE =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9203);
-    public static final int TAG_EXPOSURE_BIAS_VALUE =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9204);
-    public static final int TAG_MAX_APERTURE_VALUE =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9205);
-    public static final int TAG_SUBJECT_DISTANCE =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9206);
-    public static final int TAG_METERING_MODE =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9207);
-    public static final int TAG_LIGHT_SOURCE =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9208);
-    public static final int TAG_FLASH =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9209);
-    public static final int TAG_FOCAL_LENGTH =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x920A);
-    public static final int TAG_SUBJECT_AREA =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9214);
-    public static final int TAG_MAKER_NOTE =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x927C);
-    public static final int TAG_USER_COMMENT =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9286);
-    public static final int TAG_SUB_SEC_TIME =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9290);
-    public static final int TAG_SUB_SEC_TIME_ORIGINAL =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9291);
-    public static final int TAG_SUB_SEC_TIME_DIGITIZED =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9292);
-    public static final int TAG_FLASHPIX_VERSION =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA000);
-    public static final int TAG_COLOR_SPACE =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA001);
-    public static final int TAG_PIXEL_X_DIMENSION =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA002);
-    public static final int TAG_PIXEL_Y_DIMENSION =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA003);
-    public static final int TAG_RELATED_SOUND_FILE =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA004);
-    public static final int TAG_INTEROPERABILITY_IFD =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA005);
-    public static final int TAG_FLASH_ENERGY =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20B);
-    public static final int TAG_SPATIAL_FREQUENCY_RESPONSE =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20C);
-    public static final int TAG_FOCAL_PLANE_X_RESOLUTION =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20E);
-    public static final int TAG_FOCAL_PLANE_Y_RESOLUTION =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20F);
-    public static final int TAG_FOCAL_PLANE_RESOLUTION_UNIT =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA210);
-    public static final int TAG_SUBJECT_LOCATION =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA214);
-    public static final int TAG_EXPOSURE_INDEX =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA215);
-    public static final int TAG_SENSING_METHOD =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA217);
-    public static final int TAG_FILE_SOURCE =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA300);
-    public static final int TAG_SCENE_TYPE =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA301);
-    public static final int TAG_CFA_PATTERN =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA302);
-    public static final int TAG_CUSTOM_RENDERED =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA401);
-    public static final int TAG_EXPOSURE_MODE =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA402);
-    public static final int TAG_WHITE_BALANCE =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA403);
-    public static final int TAG_DIGITAL_ZOOM_RATIO =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA404);
-    public static final int TAG_FOCAL_LENGTH_IN_35_MM_FILE =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA405);
-    public static final int TAG_SCENE_CAPTURE_TYPE =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA406);
-    public static final int TAG_GAIN_CONTROL =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA407);
-    public static final int TAG_CONTRAST =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA408);
-    public static final int TAG_SATURATION =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA409);
-    public static final int TAG_SHARPNESS =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40A);
-    public static final int TAG_DEVICE_SETTING_DESCRIPTION =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40B);
-    public static final int TAG_SUBJECT_DISTANCE_RANGE =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40C);
-    public static final int TAG_IMAGE_UNIQUE_ID =
-        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA420);
-    // IFD GPS tags
-    public static final int TAG_GPS_VERSION_ID =
-        defineTag(IfdId.TYPE_IFD_GPS, (short) 0);
-    public static final int TAG_GPS_LATITUDE_REF =
-        defineTag(IfdId.TYPE_IFD_GPS, (short) 1);
-    public static final int TAG_GPS_LATITUDE =
-        defineTag(IfdId.TYPE_IFD_GPS, (short) 2);
-    public static final int TAG_GPS_LONGITUDE_REF =
-        defineTag(IfdId.TYPE_IFD_GPS, (short) 3);
-    public static final int TAG_GPS_LONGITUDE =
-        defineTag(IfdId.TYPE_IFD_GPS, (short) 4);
-    public static final int TAG_GPS_ALTITUDE_REF =
-        defineTag(IfdId.TYPE_IFD_GPS, (short) 5);
-    public static final int TAG_GPS_ALTITUDE =
-        defineTag(IfdId.TYPE_IFD_GPS, (short) 6);
-    public static final int TAG_GPS_TIME_STAMP =
-        defineTag(IfdId.TYPE_IFD_GPS, (short) 7);
-    public static final int TAG_GPS_SATTELLITES =
-        defineTag(IfdId.TYPE_IFD_GPS, (short) 8);
-    public static final int TAG_GPS_STATUS =
-        defineTag(IfdId.TYPE_IFD_GPS, (short) 9);
-    public static final int TAG_GPS_MEASURE_MODE =
-        defineTag(IfdId.TYPE_IFD_GPS, (short) 10);
-    public static final int TAG_GPS_DOP =
-        defineTag(IfdId.TYPE_IFD_GPS, (short) 11);
-    public static final int TAG_GPS_SPEED_REF =
-        defineTag(IfdId.TYPE_IFD_GPS, (short) 12);
-    public static final int TAG_GPS_SPEED =
-        defineTag(IfdId.TYPE_IFD_GPS, (short) 13);
-    public static final int TAG_GPS_TRACK_REF =
-        defineTag(IfdId.TYPE_IFD_GPS, (short) 14);
-    public static final int TAG_GPS_TRACK =
-        defineTag(IfdId.TYPE_IFD_GPS, (short) 15);
-    public static final int TAG_GPS_IMG_DIRECTION_REF =
-        defineTag(IfdId.TYPE_IFD_GPS, (short) 16);
-    public static final int TAG_GPS_IMG_DIRECTION =
-        defineTag(IfdId.TYPE_IFD_GPS, (short) 17);
-    public static final int TAG_GPS_MAP_DATUM =
-        defineTag(IfdId.TYPE_IFD_GPS, (short) 18);
-    public static final int TAG_GPS_DEST_LATITUDE_REF =
-        defineTag(IfdId.TYPE_IFD_GPS, (short) 19);
-    public static final int TAG_GPS_DEST_LATITUDE =
-        defineTag(IfdId.TYPE_IFD_GPS, (short) 20);
-    public static final int TAG_GPS_DEST_LONGITUDE_REF =
-        defineTag(IfdId.TYPE_IFD_GPS, (short) 21);
-    public static final int TAG_GPS_DEST_LONGITUDE =
-        defineTag(IfdId.TYPE_IFD_GPS, (short) 22);
-    public static final int TAG_GPS_DEST_BEARING_REF =
-        defineTag(IfdId.TYPE_IFD_GPS, (short) 23);
-    public static final int TAG_GPS_DEST_BEARING =
-        defineTag(IfdId.TYPE_IFD_GPS, (short) 24);
-    public static final int TAG_GPS_DEST_DISTANCE_REF =
-        defineTag(IfdId.TYPE_IFD_GPS, (short) 25);
-    public static final int TAG_GPS_DEST_DISTANCE =
-        defineTag(IfdId.TYPE_IFD_GPS, (short) 26);
-    public static final int TAG_GPS_PROCESSING_METHOD =
-        defineTag(IfdId.TYPE_IFD_GPS, (short) 27);
-    public static final int TAG_GPS_AREA_INFORMATION =
-        defineTag(IfdId.TYPE_IFD_GPS, (short) 28);
-    public static final int TAG_GPS_DATE_STAMP =
-        defineTag(IfdId.TYPE_IFD_GPS, (short) 29);
-    public static final int TAG_GPS_DIFFERENTIAL =
-        defineTag(IfdId.TYPE_IFD_GPS, (short) 30);
-    // IFD Interoperability tags
-    public static final int TAG_INTEROPERABILITY_INDEX =
-        defineTag(IfdId.TYPE_IFD_INTEROPERABILITY, (short) 1);
-
-    /**
-     * Tags that contain offset markers. These are included in the banned
-     * defines.
-     */
-    private static HashSet<Short> sOffsetTags = new HashSet<Short>();
-    static {
-        sOffsetTags.add(getTrueTagKey(TAG_GPS_IFD));
-        sOffsetTags.add(getTrueTagKey(TAG_EXIF_IFD));
-        sOffsetTags.add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT));
-        sOffsetTags.add(getTrueTagKey(TAG_INTEROPERABILITY_IFD));
-        sOffsetTags.add(getTrueTagKey(TAG_STRIP_OFFSETS));
-    }
-
-    /**
-     * Tags with definitions that cannot be overridden (banned defines).
-     */
-    protected static HashSet<Short> sBannedDefines = new HashSet<Short>(sOffsetTags);
-    static {
-        sBannedDefines.add(getTrueTagKey(TAG_NULL));
-        sBannedDefines.add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
-        sBannedDefines.add(getTrueTagKey(TAG_STRIP_BYTE_COUNTS));
-    }
-
-    /**
-     * Returns the constant representing a tag with a given TID and default IFD.
-     */
-    public static int defineTag(int ifdId, short tagId) {
-        return (tagId & 0x0000ffff) | (ifdId << 16);
-    }
-
-    /**
-     * Returns the TID for a tag constant.
-     */
-    public static short getTrueTagKey(int tag) {
-        // Truncate
-        return (short) tag;
-    }
-
-    /**
-     * Returns the default IFD for a tag constant.
-     */
-    public static int getTrueIfd(int tag) {
-        return tag >>> 16;
-    }
-
-    /**
-     * Constants for {@link TAG_ORIENTATION}. They can be interpreted as
-     * follows:
-     * <ul>
-     * <li>TOP_LEFT is the normal orientation.</li>
-     * <li>TOP_RIGHT is a left-right mirror.</li>
-     * <li>BOTTOM_LEFT is a 180 degree rotation.</li>
-     * <li>BOTTOM_RIGHT is a top-bottom mirror.</li>
-     * <li>LEFT_TOP is mirrored about the top-left<->bottom-right axis.</li>
-     * <li>RIGHT_TOP is a 90 degree clockwise rotation.</li>
-     * <li>LEFT_BOTTOM is mirrored about the top-right<->bottom-left axis.</li>
-     * <li>RIGHT_BOTTOM is a 270 degree clockwise rotation.</li>
-     * </ul>
-     */
-    public static interface Orientation {
-        public static final short TOP_LEFT = 1;
-        public static final short TOP_RIGHT = 2;
-        public static final short BOTTOM_LEFT = 3;
-        public static final short BOTTOM_RIGHT = 4;
-        public static final short LEFT_TOP = 5;
-        public static final short RIGHT_TOP = 6;
-        public static final short LEFT_BOTTOM = 7;
-        public static final short RIGHT_BOTTOM = 8;
-    }
-
-    /**
-     * Constants for {@link TAG_Y_CB_CR_POSITIONING}
-     */
-    public static interface YCbCrPositioning {
-        public static final short CENTERED = 1;
-        public static final short CO_SITED = 2;
-    }
-
-    /**
-     * Constants for {@link TAG_COMPRESSION}
-     */
-    public static interface Compression {
-        public static final short UNCOMPRESSION = 1;
-        public static final short JPEG = 6;
-    }
-
-    /**
-     * Constants for {@link TAG_RESOLUTION_UNIT}
-     */
-    public static interface ResolutionUnit {
-        public static final short INCHES = 2;
-        public static final short CENTIMETERS = 3;
-    }
-
-    /**
-     * Constants for {@link TAG_PHOTOMETRIC_INTERPRETATION}
-     */
-    public static interface PhotometricInterpretation {
-        public static final short RGB = 2;
-        public static final short YCBCR = 6;
-    }
-
-    /**
-     * Constants for {@link TAG_PLANAR_CONFIGURATION}
-     */
-    public static interface PlanarConfiguration {
-        public static final short CHUNKY = 1;
-        public static final short PLANAR = 2;
-    }
-
-    /**
-     * Constants for {@link TAG_EXPOSURE_PROGRAM}
-     */
-    public static interface ExposureProgram {
-        public static final short NOT_DEFINED = 0;
-        public static final short MANUAL = 1;
-        public static final short NORMAL_PROGRAM = 2;
-        public static final short APERTURE_PRIORITY = 3;
-        public static final short SHUTTER_PRIORITY = 4;
-        public static final short CREATIVE_PROGRAM = 5;
-        public static final short ACTION_PROGRAM = 6;
-        public static final short PROTRAIT_MODE = 7;
-        public static final short LANDSCAPE_MODE = 8;
-    }
-
-    /**
-     * Constants for {@link TAG_METERING_MODE}
-     */
-    public static interface MeteringMode {
-        public static final short UNKNOWN = 0;
-        public static final short AVERAGE = 1;
-        public static final short CENTER_WEIGHTED_AVERAGE = 2;
-        public static final short SPOT = 3;
-        public static final short MULTISPOT = 4;
-        public static final short PATTERN = 5;
-        public static final short PARTAIL = 6;
-        public static final short OTHER = 255;
-    }
-
-    /**
-     * Constants for {@link TAG_FLASH} As the definition in Jeita EXIF 2.2
-     * standard, we can treat this constant as bitwise flag.
-     * <p>
-     * e.g.
-     * <p>
-     * short flash = FIRED | RETURN_STROBE_RETURN_LIGHT_DETECTED |
-     * MODE_AUTO_MODE
-     */
-    public static interface Flash {
-        // LSB
-        public static final short DID_NOT_FIRED = 0;
-        public static final short FIRED = 1;
-        // 1st~2nd bits
-        public static final short RETURN_NO_STROBE_RETURN_DETECTION_FUNCTION = 0 << 1;
-        public static final short RETURN_STROBE_RETURN_LIGHT_NOT_DETECTED = 2 << 1;
-        public static final short RETURN_STROBE_RETURN_LIGHT_DETECTED = 3 << 1;
-        // 3rd~4th bits
-        public static final short MODE_UNKNOWN = 0 << 3;
-        public static final short MODE_COMPULSORY_FLASH_FIRING = 1 << 3;
-        public static final short MODE_COMPULSORY_FLASH_SUPPRESSION = 2 << 3;
-        public static final short MODE_AUTO_MODE = 3 << 3;
-        // 5th bit
-        public static final short FUNCTION_PRESENT = 0 << 5;
-        public static final short FUNCTION_NO_FUNCTION = 1 << 5;
-        // 6th bit
-        public static final short RED_EYE_REDUCTION_NO_OR_UNKNOWN = 0 << 6;
-        public static final short RED_EYE_REDUCTION_SUPPORT = 1 << 6;
-    }
-
-    /**
-     * Constants for {@link TAG_COLOR_SPACE}
-     */
-    public static interface ColorSpace {
-        public static final short SRGB = 1;
-        public static final short UNCALIBRATED = (short) 0xFFFF;
-    }
-
-    /**
-     * Constants for {@link TAG_EXPOSURE_MODE}
-     */
-    public static interface ExposureMode {
-        public static final short AUTO_EXPOSURE = 0;
-        public static final short MANUAL_EXPOSURE = 1;
-        public static final short AUTO_BRACKET = 2;
-    }
-
-    /**
-     * Constants for {@link TAG_WHITE_BALANCE}
-     */
-    public static interface WhiteBalance {
-        public static final short AUTO = 0;
-        public static final short MANUAL = 1;
-    }
-
-    /**
-     * Constants for {@link TAG_SCENE_CAPTURE_TYPE}
-     */
-    public static interface SceneCapture {
-        public static final short STANDARD = 0;
-        public static final short LANDSCAPE = 1;
-        public static final short PROTRAIT = 2;
-        public static final short NIGHT_SCENE = 3;
-    }
-
-    /**
-     * Constants for {@link TAG_COMPONENTS_CONFIGURATION}
-     */
-    public static interface ComponentsConfiguration {
-        public static final short NOT_EXIST = 0;
-        public static final short Y = 1;
-        public static final short CB = 2;
-        public static final short CR = 3;
-        public static final short R = 4;
-        public static final short G = 5;
-        public static final short B = 6;
-    }
-
-    /**
-     * Constants for {@link TAG_LIGHT_SOURCE}
-     */
-    public static interface LightSource {
-        public static final short UNKNOWN = 0;
-        public static final short DAYLIGHT = 1;
-        public static final short FLUORESCENT = 2;
-        public static final short TUNGSTEN = 3;
-        public static final short FLASH = 4;
-        public static final short FINE_WEATHER = 9;
-        public static final short CLOUDY_WEATHER = 10;
-        public static final short SHADE = 11;
-        public static final short DAYLIGHT_FLUORESCENT = 12;
-        public static final short DAY_WHITE_FLUORESCENT = 13;
-        public static final short COOL_WHITE_FLUORESCENT = 14;
-        public static final short WHITE_FLUORESCENT = 15;
-        public static final short STANDARD_LIGHT_A = 17;
-        public static final short STANDARD_LIGHT_B = 18;
-        public static final short STANDARD_LIGHT_C = 19;
-        public static final short D55 = 20;
-        public static final short D65 = 21;
-        public static final short D75 = 22;
-        public static final short D50 = 23;
-        public static final short ISO_STUDIO_TUNGSTEN = 24;
-        public static final short OTHER = 255;
-    }
-
-    /**
-     * Constants for {@link TAG_SENSING_METHOD}
-     */
-    public static interface SensingMethod {
-        public static final short NOT_DEFINED = 1;
-        public static final short ONE_CHIP_COLOR = 2;
-        public static final short TWO_CHIP_COLOR = 3;
-        public static final short THREE_CHIP_COLOR = 4;
-        public static final short COLOR_SEQUENTIAL_AREA = 5;
-        public static final short TRILINEAR = 7;
-        public static final short COLOR_SEQUENTIAL_LINEAR = 8;
-    }
-
-    /**
-     * Constants for {@link TAG_FILE_SOURCE}
-     */
-    public static interface FileSource {
-        public static final short DSC = 3;
-    }
-
-    /**
-     * Constants for {@link TAG_SCENE_TYPE}
-     */
-    public static interface SceneType {
-        public static final short DIRECT_PHOTOGRAPHED = 1;
-    }
-
-    /**
-     * Constants for {@link TAG_GAIN_CONTROL}
-     */
-    public static interface GainControl {
-        public static final short NONE = 0;
-        public static final short LOW_UP = 1;
-        public static final short HIGH_UP = 2;
-        public static final short LOW_DOWN = 3;
-        public static final short HIGH_DOWN = 4;
-    }
-
-    /**
-     * Constants for {@link TAG_CONTRAST}
-     */
-    public static interface Contrast {
-        public static final short NORMAL = 0;
-        public static final short SOFT = 1;
-        public static final short HARD = 2;
-    }
-
-    /**
-     * Constants for {@link TAG_SATURATION}
-     */
-    public static interface Saturation {
-        public static final short NORMAL = 0;
-        public static final short LOW = 1;
-        public static final short HIGH = 2;
-    }
-
-    /**
-     * Constants for {@link TAG_SHARPNESS}
-     */
-    public static interface Sharpness {
-        public static final short NORMAL = 0;
-        public static final short SOFT = 1;
-        public static final short HARD = 2;
-    }
-
-    /**
-     * Constants for {@link TAG_SUBJECT_DISTANCE}
-     */
-    public static interface SubjectDistance {
-        public static final short UNKNOWN = 0;
-        public static final short MACRO = 1;
-        public static final short CLOSE_VIEW = 2;
-        public static final short DISTANT_VIEW = 3;
-    }
-
-    /**
-     * Constants for {@link TAG_GPS_LATITUDE_REF},
-     * {@link TAG_GPS_DEST_LATITUDE_REF}
-     */
-    public static interface GpsLatitudeRef {
-        public static final String NORTH = "N";
-        public static final String SOUTH = "S";
-    }
-
-    /**
-     * Constants for {@link TAG_GPS_LONGITUDE_REF},
-     * {@link TAG_GPS_DEST_LONGITUDE_REF}
-     */
-    public static interface GpsLongitudeRef {
-        public static final String EAST = "E";
-        public static final String WEST = "W";
-    }
-
-    /**
-     * Constants for {@link TAG_GPS_ALTITUDE_REF}
-     */
-    public static interface GpsAltitudeRef {
-        public static final short SEA_LEVEL = 0;
-        public static final short SEA_LEVEL_NEGATIVE = 1;
-    }
-
-    /**
-     * Constants for {@link TAG_GPS_STATUS}
-     */
-    public static interface GpsStatus {
-        public static final String IN_PROGRESS = "A";
-        public static final String INTEROPERABILITY = "V";
-    }
-
-    /**
-     * Constants for {@link TAG_GPS_MEASURE_MODE}
-     */
-    public static interface GpsMeasureMode {
-        public static final String MODE_2_DIMENSIONAL = "2";
-        public static final String MODE_3_DIMENSIONAL = "3";
-    }
-
-    /**
-     * Constants for {@link TAG_GPS_SPEED_REF},
-     * {@link TAG_GPS_DEST_DISTANCE_REF}
-     */
-    public static interface GpsSpeedRef {
-        public static final String KILOMETERS = "K";
-        public static final String MILES = "M";
-        public static final String KNOTS = "N";
-    }
-
-    /**
-     * Constants for {@link TAG_GPS_TRACK_REF},
-     * {@link TAG_GPS_IMG_DIRECTION_REF}, {@link TAG_GPS_DEST_BEARING_REF}
-     */
-    public static interface GpsTrackRef {
-        public static final String TRUE_DIRECTION = "T";
-        public static final String MAGNETIC_DIRECTION = "M";
-    }
-
-    /**
-     * Constants for {@link TAG_GPS_DIFFERENTIAL}
-     */
-    public static interface GpsDifferential {
-        public static final short WITHOUT_DIFFERENTIAL_CORRECTION = 0;
-        public static final short DIFFERENTIAL_CORRECTION_APPLIED = 1;
-    }
-
-    private static final String NULL_ARGUMENT_STRING = "Argument is null";
-    private ExifData mData = new ExifData(DEFAULT_BYTE_ORDER);
-    public static final ByteOrder DEFAULT_BYTE_ORDER = ByteOrder.BIG_ENDIAN;
-
-    public ExifInterface() {
-        mGPSDateStampFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
-    }
-
-    /**
-     * Reads the exif tags from a byte array, clearing this ExifInterface
-     * object's existing exif tags.
-     *
-     * @param jpeg a byte array containing a jpeg compressed image.
-     * @throws IOException
-     */
-    public void readExif(byte[] jpeg) throws IOException {
-        readExif(new ByteArrayInputStream(jpeg));
-    }
-
-    /**
-     * Reads the exif tags from an InputStream, clearing this ExifInterface
-     * object's existing exif tags.
-     *
-     * @param inStream an InputStream containing a jpeg compressed image.
-     * @throws IOException
-     */
-    public void readExif(InputStream inStream) throws IOException {
-        if (inStream == null) {
-            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
-        }
-        ExifData d = null;
-        try {
-            d = new ExifReader(this).read(inStream);
-        } catch (ExifInvalidFormatException e) {
-            throw new IOException("Invalid exif format : " + e);
-        }
-        mData = d;
-    }
-
-    /**
-     * Reads the exif tags from a file, clearing this ExifInterface object's
-     * existing exif tags.
-     *
-     * @param inFileName a string representing the filepath to jpeg file.
-     * @throws FileNotFoundException
-     * @throws IOException
-     */
-    public void readExif(String inFileName) throws FileNotFoundException, IOException {
-        if (inFileName == null) {
-            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
-        }
-        InputStream is = null;
-        try {
-            is = (InputStream) new BufferedInputStream(new FileInputStream(inFileName));
-            readExif(is);
-        } catch (IOException e) {
-            closeSilently(is);
-            throw e;
-        }
-        is.close();
-    }
-
-    /**
-     * Sets the exif tags, clearing this ExifInterface object's existing exif
-     * tags.
-     *
-     * @param tags a collection of exif tags to set.
-     */
-    public void setExif(Collection<ExifTag> tags) {
-        clearExif();
-        setTags(tags);
-    }
-
-    /**
-     * Clears this ExifInterface object's existing exif tags.
-     */
-    public void clearExif() {
-        mData = new ExifData(DEFAULT_BYTE_ORDER);
-    }
-
-    /**
-     * Writes the tags from this ExifInterface object into a jpeg image,
-     * removing prior exif tags.
-     *
-     * @param jpeg a byte array containing a jpeg compressed image.
-     * @param exifOutStream an OutputStream to which the jpeg image with added
-     *            exif tags will be written.
-     * @throws IOException
-     */
-    public void writeExif(byte[] jpeg, OutputStream exifOutStream) throws IOException {
-        if (jpeg == null || exifOutStream == null) {
-            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
-        }
-        OutputStream s = getExifWriterStream(exifOutStream);
-        s.write(jpeg, 0, jpeg.length);
-        s.flush();
-    }
-
-    /**
-     * Writes the tags from this ExifInterface object into a jpeg compressed
-     * bitmap, removing prior exif tags.
-     *
-     * @param bmap a bitmap to compress and write exif into.
-     * @param exifOutStream the OutputStream to which the jpeg image with added
-     *            exif tags will be written.
-     * @throws IOException
-     */
-    public void writeExif(Bitmap bmap, OutputStream exifOutStream) throws IOException {
-        if (bmap == null || exifOutStream == null) {
-            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
-        }
-        OutputStream s = getExifWriterStream(exifOutStream);
-        bmap.compress(Bitmap.CompressFormat.JPEG, 90, s);
-        s.flush();
-    }
-
-    /**
-     * Writes the tags from this ExifInterface object into a jpeg stream,
-     * removing prior exif tags.
-     *
-     * @param jpegStream an InputStream containing a jpeg compressed image.
-     * @param exifOutStream an OutputStream to which the jpeg image with added
-     *            exif tags will be written.
-     * @throws IOException
-     */
-    public void writeExif(InputStream jpegStream, OutputStream exifOutStream) throws IOException {
-        if (jpegStream == null || exifOutStream == null) {
-            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
-        }
-        OutputStream s = getExifWriterStream(exifOutStream);
-        doExifStreamIO(jpegStream, s);
-        s.flush();
-    }
-
-    /**
-     * Writes the tags from this ExifInterface object into a jpeg image,
-     * removing prior exif tags.
-     *
-     * @param jpeg a byte array containing a jpeg compressed image.
-     * @param exifOutFileName a String containing the filepath to which the jpeg
-     *            image with added exif tags will be written.
-     * @throws FileNotFoundException
-     * @throws IOException
-     */
-    public void writeExif(byte[] jpeg, String exifOutFileName) throws FileNotFoundException,
-            IOException {
-        if (jpeg == null || exifOutFileName == null) {
-            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
-        }
-        OutputStream s = null;
-        try {
-            s = getExifWriterStream(exifOutFileName);
-            s.write(jpeg, 0, jpeg.length);
-            s.flush();
-        } catch (IOException e) {
-            closeSilently(s);
-            throw e;
-        }
-        s.close();
-    }
-
-    /**
-     * Writes the tags from this ExifInterface object into a jpeg compressed
-     * bitmap, removing prior exif tags.
-     *
-     * @param bmap a bitmap to compress and write exif into.
-     * @param exifOutFileName a String containing the filepath to which the jpeg
-     *            image with added exif tags will be written.
-     * @throws FileNotFoundException
-     * @throws IOException
-     */
-    public void writeExif(Bitmap bmap, String exifOutFileName) throws FileNotFoundException,
-            IOException {
-        if (bmap == null || exifOutFileName == null) {
-            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
-        }
-        OutputStream s = null;
-        try {
-            s = getExifWriterStream(exifOutFileName);
-            bmap.compress(Bitmap.CompressFormat.JPEG, 90, s);
-            s.flush();
-        } catch (IOException e) {
-            closeSilently(s);
-            throw e;
-        }
-        s.close();
-    }
-
-    /**
-     * Writes the tags from this ExifInterface object into a jpeg stream,
-     * removing prior exif tags.
-     *
-     * @param jpegStream an InputStream containing a jpeg compressed image.
-     * @param exifOutFileName a String containing the filepath to which the jpeg
-     *            image with added exif tags will be written.
-     * @throws FileNotFoundException
-     * @throws IOException
-     */
-    public void writeExif(InputStream jpegStream, String exifOutFileName)
-            throws FileNotFoundException, IOException {
-        if (jpegStream == null || exifOutFileName == null) {
-            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
-        }
-        OutputStream s = null;
-        try {
-            s = getExifWriterStream(exifOutFileName);
-            doExifStreamIO(jpegStream, s);
-            s.flush();
-        } catch (IOException e) {
-            closeSilently(s);
-            throw e;
-        }
-        s.close();
-    }
-
-    /**
-     * Writes the tags from this ExifInterface object into a jpeg file, removing
-     * prior exif tags.
-     *
-     * @param jpegFileName a String containing the filepath for a jpeg file.
-     * @param exifOutFileName a String containing the filepath to which the jpeg
-     *            image with added exif tags will be written.
-     * @throws FileNotFoundException
-     * @throws IOException
-     */
-    public void writeExif(String jpegFileName, String exifOutFileName)
-            throws FileNotFoundException, IOException {
-        if (jpegFileName == null || exifOutFileName == null) {
-            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
-        }
-        InputStream is = null;
-        try {
-            is = new FileInputStream(jpegFileName);
-            writeExif(is, exifOutFileName);
-        } catch (IOException e) {
-            closeSilently(is);
-            throw e;
-        }
-        is.close();
-    }
-
-    /**
-     * Wraps an OutputStream object with an ExifOutputStream. Exif tags in this
-     * ExifInterface object will be added to a jpeg image written to this
-     * stream, removing prior exif tags. Other methods of this ExifInterface
-     * object should not be called until the returned OutputStream has been
-     * closed.
-     *
-     * @param outStream an OutputStream to wrap.
-     * @return an OutputStream that wraps the outStream parameter, and adds exif
-     *         metadata. A jpeg image should be written to this stream.
-     */
-    public OutputStream getExifWriterStream(OutputStream outStream) {
-        if (outStream == null) {
-            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
-        }
-        ExifOutputStream eos = new ExifOutputStream(outStream, this);
-        eos.setExifData(mData);
-        return eos;
-    }
-
-    /**
-     * Returns an OutputStream object that writes to a file. Exif tags in this
-     * ExifInterface object will be added to a jpeg image written to this
-     * stream, removing prior exif tags. Other methods of this ExifInterface
-     * object should not be called until the returned OutputStream has been
-     * closed.
-     *
-     * @param exifOutFileName an String containing a filepath for a jpeg file.
-     * @return an OutputStream that writes to the exifOutFileName file, and adds
-     *         exif metadata. A jpeg image should be written to this stream.
-     * @throws FileNotFoundException
-     */
-    public OutputStream getExifWriterStream(String exifOutFileName) throws FileNotFoundException {
-        if (exifOutFileName == null) {
-            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
-        }
-        OutputStream out = null;
-        try {
-            out = (OutputStream) new FileOutputStream(exifOutFileName);
-        } catch (FileNotFoundException e) {
-            closeSilently(out);
-            throw e;
-        }
-        return getExifWriterStream(out);
-    }
-
-    /**
-     * Attempts to do an in-place rewrite the exif metadata in a file for the
-     * given tags. If tags do not exist or do not have the same size as the
-     * existing exif tags, this method will fail.
-     *
-     * @param filename a String containing a filepath for a jpeg file with exif
-     *            tags to rewrite.
-     * @param tags tags that will be written into the jpeg file over existing
-     *            tags if possible.
-     * @return true if success, false if could not overwrite. If false, no
-     *         changes are made to the file.
-     * @throws FileNotFoundException
-     * @throws IOException
-     */
-    public boolean rewriteExif(String filename, Collection<ExifTag> tags)
-            throws FileNotFoundException, IOException {
-        RandomAccessFile file = null;
-        InputStream is = null;
-        boolean ret;
-        try {
-            File temp = new File(filename);
-            is = new BufferedInputStream(new FileInputStream(temp));
-
-            // Parse beginning of APP1 in exif to find size of exif header.
-            ExifParser parser = null;
-            try {
-                parser = ExifParser.parse(is, this);
-            } catch (ExifInvalidFormatException e) {
-                throw new IOException("Invalid exif format : ", e);
-            }
-            long exifSize = parser.getOffsetToExifEndFromSOF();
-
-            // Free up resources
-            is.close();
-            is = null;
-
-            // Open file for memory mapping.
-            file = new RandomAccessFile(temp, "rw");
-            long fileLength = file.length();
-            if (fileLength < exifSize) {
-                throw new IOException("Filesize changed during operation");
-            }
-
-            // Map only exif header into memory.
-            ByteBuffer buf = file.getChannel().map(MapMode.READ_WRITE, 0, exifSize);
-
-            // Attempt to overwrite tag values without changing lengths (avoids
-            // file copy).
-            ret = rewriteExif(buf, tags);
-        } catch (IOException e) {
-            closeSilently(file);
-            throw e;
-        } finally {
-            closeSilently(is);
-        }
-        file.close();
-        return ret;
-    }
-
-    /**
-     * Attempts to do an in-place rewrite the exif metadata in a ByteBuffer for
-     * the given tags. If tags do not exist or do not have the same size as the
-     * existing exif tags, this method will fail.
-     *
-     * @param buf a ByteBuffer containing a jpeg file with existing exif tags to
-     *            rewrite.
-     * @param tags tags that will be written into the jpeg ByteBuffer over
-     *            existing tags if possible.
-     * @return true if success, false if could not overwrite. If false, no
-     *         changes are made to the ByteBuffer.
-     * @throws IOException
-     */
-    public boolean rewriteExif(ByteBuffer buf, Collection<ExifTag> tags) throws IOException {
-        ExifModifier mod = null;
-        try {
-            mod = new ExifModifier(buf, this);
-            for (ExifTag t : tags) {
-                mod.modifyTag(t);
-            }
-            return mod.commit();
-        } catch (ExifInvalidFormatException e) {
-            throw new IOException("Invalid exif format : " + e);
-        }
-    }
-
-    /**
-     * Attempts to do an in-place rewrite of the exif metadata. If this fails,
-     * fall back to overwriting file. This preserves tags that are not being
-     * rewritten.
-     *
-     * @param filename a String containing a filepath for a jpeg file.
-     * @param tags tags that will be written into the jpeg file over existing
-     *            tags if possible.
-     * @throws FileNotFoundException
-     * @throws IOException
-     * @see #rewriteExif
-     */
-    public void forceRewriteExif(String filename, Collection<ExifTag> tags)
-            throws FileNotFoundException,
-            IOException {
-        // Attempt in-place write
-        if (!rewriteExif(filename, tags)) {
-            // Fall back to doing a copy
-            ExifData tempData = mData;
-            mData = new ExifData(DEFAULT_BYTE_ORDER);
-            FileInputStream is = null;
-            ByteArrayOutputStream bytes = null;
-            try {
-                is = new FileInputStream(filename);
-                bytes = new ByteArrayOutputStream();
-                doExifStreamIO(is, bytes);
-                byte[] imageBytes = bytes.toByteArray();
-                readExif(imageBytes);
-                setTags(tags);
-                writeExif(imageBytes, filename);
-            } catch (IOException e) {
-                closeSilently(is);
-                throw e;
-            } finally {
-                is.close();
-                // Prevent clobbering of mData
-                mData = tempData;
-            }
-        }
-    }
-
-    /**
-     * Attempts to do an in-place rewrite of the exif metadata using the tags in
-     * this ExifInterface object. If this fails, fall back to overwriting file.
-     * This preserves tags that are not being rewritten.
-     *
-     * @param filename a String containing a filepath for a jpeg file.
-     * @throws FileNotFoundException
-     * @throws IOException
-     * @see #rewriteExif
-     */
-    public void forceRewriteExif(String filename) throws FileNotFoundException, IOException {
-        forceRewriteExif(filename, getAllTags());
-    }
-
-    /**
-     * Get the exif tags in this ExifInterface object or null if none exist.
-     *
-     * @return a List of {@link ExifTag}s.
-     */
-    public List<ExifTag> getAllTags() {
-        return mData.getAllTags();
-    }
-
-    /**
-     * Returns a list of ExifTags that share a TID (which can be obtained by
-     * calling {@link #getTrueTagKey} on a defined tag constant) or null if none
-     * exist.
-     *
-     * @param tagId a TID as defined in the exif standard (or with
-     *            {@link #defineTag}).
-     * @return a List of {@link ExifTag}s.
-     */
-    public List<ExifTag> getTagsForTagId(short tagId) {
-        return mData.getAllTagsForTagId(tagId);
-    }
-
-    /**
-     * Returns a list of ExifTags that share an IFD (which can be obtained by
-     * calling {@link #getTrueIFD} on a defined tag constant) or null if none
-     * exist.
-     *
-     * @param ifdId an IFD as defined in the exif standard (or with
-     *            {@link #defineTag}).
-     * @return a List of {@link ExifTag}s.
-     */
-    public List<ExifTag> getTagsForIfdId(int ifdId) {
-        return mData.getAllTagsForIfd(ifdId);
-    }
-
-    /**
-     * Gets an ExifTag for an IFD other than the tag's default.
-     *
-     * @see #getTag
-     */
-    public ExifTag getTag(int tagId, int ifdId) {
-        if (!ExifTag.isValidIfd(ifdId)) {
-            return null;
-        }
-        return mData.getTag(getTrueTagKey(tagId), ifdId);
-    }
-
-    /**
-     * Returns the ExifTag in that tag's default IFD for a defined tag constant
-     * or null if none exists.
-     *
-     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
-     * @return an {@link ExifTag} or null if none exists.
-     */
-    public ExifTag getTag(int tagId) {
-        int ifdId = getDefinedTagDefaultIfd(tagId);
-        return getTag(tagId, ifdId);
-    }
-
-    /**
-     * Gets a tag value for an IFD other than the tag's default.
-     *
-     * @see #getTagValue
-     */
-    public Object getTagValue(int tagId, int ifdId) {
-        ExifTag t = getTag(tagId, ifdId);
-        return (t == null) ? null : t.getValue();
-    }
-
-    /**
-     * Returns the value of the ExifTag in that tag's default IFD for a defined
-     * tag constant or null if none exists or the value could not be cast into
-     * the return type.
-     *
-     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
-     * @return the value of the ExifTag or null if none exists.
-     */
-    public Object getTagValue(int tagId) {
-        int ifdId = getDefinedTagDefaultIfd(tagId);
-        return getTagValue(tagId, ifdId);
-    }
-
-    /*
-     * Getter methods that are similar to getTagValue. Null is returned if the
-     * tag value cannot be cast into the return type.
-     */
-
-    /**
-     * @see #getTagValue
-     */
-    public String getTagStringValue(int tagId, int ifdId) {
-        ExifTag t = getTag(tagId, ifdId);
-        if (t == null) {
-            return null;
-        }
-        return t.getValueAsString();
-    }
-
-    /**
-     * @see #getTagValue
-     */
-    public String getTagStringValue(int tagId) {
-        int ifdId = getDefinedTagDefaultIfd(tagId);
-        return getTagStringValue(tagId, ifdId);
-    }
-
-    /**
-     * @see #getTagValue
-     */
-    public Long getTagLongValue(int tagId, int ifdId) {
-        long[] l = getTagLongValues(tagId, ifdId);
-        if (l == null || l.length <= 0) {
-            return null;
-        }
-        return Long.valueOf(l[0]);
-    }
-
-    /**
-     * @see #getTagValue
-     */
-    public Long getTagLongValue(int tagId) {
-        int ifdId = getDefinedTagDefaultIfd(tagId);
-        return getTagLongValue(tagId, ifdId);
-    }
-
-    /**
-     * @see #getTagValue
-     */
-    public Integer getTagIntValue(int tagId, int ifdId) {
-        int[] l = getTagIntValues(tagId, ifdId);
-        if (l == null || l.length <= 0) {
-            return null;
-        }
-        return Integer.valueOf(l[0]);
-    }
-
-    /**
-     * @see #getTagValue
-     */
-    public Integer getTagIntValue(int tagId) {
-        int ifdId = getDefinedTagDefaultIfd(tagId);
-        return getTagIntValue(tagId, ifdId);
-    }
-
-    /**
-     * @see #getTagValue
-     */
-    public Byte getTagByteValue(int tagId, int ifdId) {
-        byte[] l = getTagByteValues(tagId, ifdId);
-        if (l == null || l.length <= 0) {
-            return null;
-        }
-        return Byte.valueOf(l[0]);
-    }
-
-    /**
-     * @see #getTagValue
-     */
-    public Byte getTagByteValue(int tagId) {
-        int ifdId = getDefinedTagDefaultIfd(tagId);
-        return getTagByteValue(tagId, ifdId);
-    }
-
-    /**
-     * @see #getTagValue
-     */
-    public Rational getTagRationalValue(int tagId, int ifdId) {
-        Rational[] l = getTagRationalValues(tagId, ifdId);
-        if (l == null || l.length == 0) {
-            return null;
-        }
-        return new Rational(l[0]);
-    }
-
-    /**
-     * @see #getTagValue
-     */
-    public Rational getTagRationalValue(int tagId) {
-        int ifdId = getDefinedTagDefaultIfd(tagId);
-        return getTagRationalValue(tagId, ifdId);
-    }
-
-    /**
-     * @see #getTagValue
-     */
-    public long[] getTagLongValues(int tagId, int ifdId) {
-        ExifTag t = getTag(tagId, ifdId);
-        if (t == null) {
-            return null;
-        }
-        return t.getValueAsLongs();
-    }
-
-    /**
-     * @see #getTagValue
-     */
-    public long[] getTagLongValues(int tagId) {
-        int ifdId = getDefinedTagDefaultIfd(tagId);
-        return getTagLongValues(tagId, ifdId);
-    }
-
-    /**
-     * @see #getTagValue
-     */
-    public int[] getTagIntValues(int tagId, int ifdId) {
-        ExifTag t = getTag(tagId, ifdId);
-        if (t == null) {
-            return null;
-        }
-        return t.getValueAsInts();
-    }
-
-    /**
-     * @see #getTagValue
-     */
-    public int[] getTagIntValues(int tagId) {
-        int ifdId = getDefinedTagDefaultIfd(tagId);
-        return getTagIntValues(tagId, ifdId);
-    }
-
-    /**
-     * @see #getTagValue
-     */
-    public byte[] getTagByteValues(int tagId, int ifdId) {
-        ExifTag t = getTag(tagId, ifdId);
-        if (t == null) {
-            return null;
-        }
-        return t.getValueAsBytes();
-    }
-
-    /**
-     * @see #getTagValue
-     */
-    public byte[] getTagByteValues(int tagId) {
-        int ifdId = getDefinedTagDefaultIfd(tagId);
-        return getTagByteValues(tagId, ifdId);
-    }
-
-    /**
-     * @see #getTagValue
-     */
-    public Rational[] getTagRationalValues(int tagId, int ifdId) {
-        ExifTag t = getTag(tagId, ifdId);
-        if (t == null) {
-            return null;
-        }
-        return t.getValueAsRationals();
-    }
-
-    /**
-     * @see #getTagValue
-     */
-    public Rational[] getTagRationalValues(int tagId) {
-        int ifdId = getDefinedTagDefaultIfd(tagId);
-        return getTagRationalValues(tagId, ifdId);
-    }
-
-    /**
-     * Checks whether a tag has a defined number of elements.
-     *
-     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
-     * @return true if the tag has a defined number of elements.
-     */
-    public boolean isTagCountDefined(int tagId) {
-        int info = getTagInfo().get(tagId);
-        // No value in info can be zero, as all tags have a non-zero type
-        if (info == 0) {
-            return false;
-        }
-        return getComponentCountFromInfo(info) != ExifTag.SIZE_UNDEFINED;
-    }
-
-    /**
-     * Gets the defined number of elements for a tag.
-     *
-     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
-     * @return the number of elements or {@link ExifTag#SIZE_UNDEFINED} if the
-     *         tag or the number of elements is not defined.
-     */
-    public int getDefinedTagCount(int tagId) {
-        int info = getTagInfo().get(tagId);
-        if (info == 0) {
-            return ExifTag.SIZE_UNDEFINED;
-        }
-        return getComponentCountFromInfo(info);
-    }
-
-    /**
-     * Gets the number of elements for an ExifTag in a given IFD.
-     *
-     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
-     * @param ifdId the IFD containing the ExifTag to check.
-     * @return the number of elements in the ExifTag, if the tag's size is
-     *         undefined this will return the actual number of elements that is
-     *         in the ExifTag's value.
-     */
-    public int getActualTagCount(int tagId, int ifdId) {
-        ExifTag t = getTag(tagId, ifdId);
-        if (t == null) {
-            return 0;
-        }
-        return t.getComponentCount();
-    }
-
-    /**
-     * Gets the default IFD for a tag.
-     *
-     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
-     * @return the default IFD for a tag definition or {@link #IFD_NULL} if no
-     *         definition exists.
-     */
-    public int getDefinedTagDefaultIfd(int tagId) {
-        int info = getTagInfo().get(tagId);
-        if (info == DEFINITION_NULL) {
-            return IFD_NULL;
-        }
-        return getTrueIfd(tagId);
-    }
-
-    /**
-     * Gets the defined type for a tag.
-     *
-     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
-     * @return the type.
-     * @see ExifTag#getDataType()
-     */
-    public short getDefinedTagType(int tagId) {
-        int info = getTagInfo().get(tagId);
-        if (info == 0) {
-            return -1;
-        }
-        return getTypeFromInfo(info);
-    }
-
-    /**
-     * Returns true if tag TID is one of the following: {@link TAG_EXIF_IFD},
-     * {@link TAG_GPS_IFD}, {@link TAG_JPEG_INTERCHANGE_FORMAT},
-     * {@link TAG_STRIP_OFFSETS}, {@link TAG_INTEROPERABILITY_IFD}
-     * <p>
-     * Note: defining tags with these TID's is disallowed.
-     *
-     * @param tag a tag's TID (can be obtained from a defined tag constant with
-     *            {@link #getTrueTagKey}).
-     * @return true if the TID is that of an offset tag.
-     */
-    protected static boolean isOffsetTag(short tag) {
-        return sOffsetTags.contains(tag);
-    }
-
-    /**
-     * Creates a tag for a defined tag constant in a given IFD if that IFD is
-     * allowed for the tag.  This method will fail anytime the appropriate
-     * {@link ExifTag#setValue} for this tag's datatype would fail.
-     *
-     * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
-     * @param ifdId the IFD that the tag should be in.
-     * @param val the value of the tag to set.
-     * @return an ExifTag object or null if one could not be constructed.
-     * @see #buildTag
-     */
-    public ExifTag buildTag(int tagId, int ifdId, Object val) {
-        int info = getTagInfo().get(tagId);
-        if (info == 0 || val == null) {
-            return null;
-        }
-        short type = getTypeFromInfo(info);
-        int definedCount = getComponentCountFromInfo(info);
-        boolean hasDefinedCount = (definedCount != ExifTag.SIZE_UNDEFINED);
-        if (!ExifInterface.isIfdAllowed(info, ifdId)) {
-            return null;
-        }
-        ExifTag t = new ExifTag(getTrueTagKey(tagId), type, definedCount, ifdId, hasDefinedCount);
-        if (!t.setValue(val)) {
-            return null;
-        }
-        return t;
-    }
-
-    /**
-     * Creates a tag for a defined tag constant in the tag's default IFD.
-     *
-     * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
-     * @param val the tag's value.
-     * @return an ExifTag object.
-     */
-    public ExifTag buildTag(int tagId, Object val) {
-        int ifdId = getTrueIfd(tagId);
-        return buildTag(tagId, ifdId, val);
-    }
-
-    protected ExifTag buildUninitializedTag(int tagId) {
-        int info = getTagInfo().get(tagId);
-        if (info == 0) {
-            return null;
-        }
-        short type = getTypeFromInfo(info);
-        int definedCount = getComponentCountFromInfo(info);
-        boolean hasDefinedCount = (definedCount != ExifTag.SIZE_UNDEFINED);
-        int ifdId = getTrueIfd(tagId);
-        ExifTag t = new ExifTag(getTrueTagKey(tagId), type, definedCount, ifdId, hasDefinedCount);
-        return t;
-    }
-
-    /**
-     * Sets the value of an ExifTag if it exists in the given IFD. The value
-     * must be the correct type and length for that ExifTag.
-     *
-     * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
-     * @param ifdId the IFD that the ExifTag is in.
-     * @param val the value to set.
-     * @return true if success, false if the ExifTag doesn't exist or the value
-     *         is the wrong type/length.
-     * @see #setTagValue
-     */
-    public boolean setTagValue(int tagId, int ifdId, Object val) {
-        ExifTag t = getTag(tagId, ifdId);
-        if (t == null) {
-            return false;
-        }
-        return t.setValue(val);
-    }
-
-    /**
-     * Sets the value of an ExifTag if it exists it's default IFD. The value
-     * must be the correct type and length for that ExifTag.
-     *
-     * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
-     * @param val the value to set.
-     * @return true if success, false if the ExifTag doesn't exist or the value
-     *         is the wrong type/length.
-     */
-    public boolean setTagValue(int tagId, Object val) {
-        int ifdId = getDefinedTagDefaultIfd(tagId);
-        return setTagValue(tagId, ifdId, val);
-    }
-
-    /**
-     * Puts an ExifTag into this ExifInterface object's tags, removing a
-     * previous ExifTag with the same TID and IFD. The IFD it is put into will
-     * be the one the tag was created with in {@link #buildTag}.
-     *
-     * @param tag an ExifTag to put into this ExifInterface's tags.
-     * @return the previous ExifTag with the same TID and IFD or null if none
-     *         exists.
-     */
-    public ExifTag setTag(ExifTag tag) {
-        return mData.addTag(tag);
-    }
-
-    /**
-     * Puts a collection of ExifTags into this ExifInterface objects's tags. Any
-     * previous ExifTags with the same TID and IFDs will be removed.
-     *
-     * @param tags a Collection of ExifTags.
-     * @see #setTag
-     */
-    public void setTags(Collection<ExifTag> tags) {
-        for (ExifTag t : tags) {
-            setTag(t);
-        }
-    }
-
-    /**
-     * Removes the ExifTag for a tag constant from the given IFD.
-     *
-     * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
-     * @param ifdId the IFD of the ExifTag to remove.
-     */
-    public void deleteTag(int tagId, int ifdId) {
-        mData.removeTag(getTrueTagKey(tagId), ifdId);
-    }
-
-    /**
-     * Removes the ExifTag for a tag constant from that tag's default IFD.
-     *
-     * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
-     */
-    public void deleteTag(int tagId) {
-        int ifdId = getDefinedTagDefaultIfd(tagId);
-        deleteTag(tagId, ifdId);
-    }
-
-    /**
-     * Creates a new tag definition in this ExifInterface object for a given TID
-     * and default IFD. Creating a definition with the same TID and default IFD
-     * as a previous definition will override it.
-     *
-     * @param tagId the TID for the tag.
-     * @param defaultIfd the default IFD for the tag.
-     * @param tagType the type of the tag (see {@link ExifTag#getDataType()}).
-     * @param defaultComponentCount the number of elements of this tag's type in
-     *            the tags value.
-     * @param allowedIfds the IFD's this tag is allowed to be put in.
-     * @return the defined tag constant (e.g. {@link #TAG_IMAGE_WIDTH}) or
-     *         {@link #TAG_NULL} if the definition could not be made.
-     */
-    public int setTagDefinition(short tagId, int defaultIfd, short tagType,
-            short defaultComponentCount, int[] allowedIfds) {
-        if (sBannedDefines.contains(tagId)) {
-            return TAG_NULL;
-        }
-        if (ExifTag.isValidType(tagType) && ExifTag.isValidIfd(defaultIfd)) {
-            int tagDef = defineTag(defaultIfd, tagId);
-            if (tagDef == TAG_NULL) {
-                return TAG_NULL;
-            }
-            int[] otherDefs = getTagDefinitionsForTagId(tagId);
-            SparseIntArray infos = getTagInfo();
-            // Make sure defaultIfd is in allowedIfds
-            boolean defaultCheck = false;
-            for (int i : allowedIfds) {
-                if (defaultIfd == i) {
-                    defaultCheck = true;
-                }
-                if (!ExifTag.isValidIfd(i)) {
-                    return TAG_NULL;
-                }
-            }
-            if (!defaultCheck) {
-                return TAG_NULL;
-            }
-
-            int ifdFlags = getFlagsFromAllowedIfds(allowedIfds);
-            // Make sure no identical tags can exist in allowedIfds
-            if (otherDefs != null) {
-                for (int def : otherDefs) {
-                    int tagInfo = infos.get(def);
-                    int allowedFlags = getAllowedIfdFlagsFromInfo(tagInfo);
-                    if ((ifdFlags & allowedFlags) != 0) {
-                        return TAG_NULL;
-                    }
-                }
-            }
-            getTagInfo().put(tagDef, ifdFlags << 24 | (tagType << 16) | defaultComponentCount);
-            return tagDef;
-        }
-        return TAG_NULL;
-    }
-
-    protected int getTagDefinition(short tagId, int defaultIfd) {
-        return getTagInfo().get(defineTag(defaultIfd, tagId));
-    }
-
-    protected int[] getTagDefinitionsForTagId(short tagId) {
-        int[] ifds = IfdData.getIfds();
-        int[] defs = new int[ifds.length];
-        int counter = 0;
-        SparseIntArray infos = getTagInfo();
-        for (int i : ifds) {
-            int def = defineTag(i, tagId);
-            if (infos.get(def) != DEFINITION_NULL) {
-                defs[counter++] = def;
-            }
-        }
-        if (counter == 0) {
-            return null;
-        }
-
-        return Arrays.copyOfRange(defs, 0, counter);
-    }
-
-    protected int getTagDefinitionForTag(ExifTag tag) {
-        short type = tag.getDataType();
-        int count = tag.getComponentCount();
-        int ifd = tag.getIfd();
-        return getTagDefinitionForTag(tag.getTagId(), type, count, ifd);
-    }
-
-    protected int getTagDefinitionForTag(short tagId, short type, int count, int ifd) {
-        int[] defs = getTagDefinitionsForTagId(tagId);
-        if (defs == null) {
-            return TAG_NULL;
-        }
-        SparseIntArray infos = getTagInfo();
-        int ret = TAG_NULL;
-        for (int i : defs) {
-            int info = infos.get(i);
-            short def_type = getTypeFromInfo(info);
-            int def_count = getComponentCountFromInfo(info);
-            int[] def_ifds = getAllowedIfdsFromInfo(info);
-            boolean valid_ifd = false;
-            for (int j : def_ifds) {
-                if (j == ifd) {
-                    valid_ifd = true;
-                    break;
-                }
-            }
-            if (valid_ifd && type == def_type
-                    && (count == def_count || def_count == ExifTag.SIZE_UNDEFINED)) {
-                ret = i;
-                break;
-            }
-        }
-        return ret;
-    }
-
-    /**
-     * Removes a tag definition for given defined tag constant.
-     *
-     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
-     */
-    public void removeTagDefinition(int tagId) {
-        getTagInfo().delete(tagId);
-    }
-
-    /**
-     * Resets tag definitions to the default ones.
-     */
-    public void resetTagDefinitions() {
-        mTagInfo = null;
-    }
-
-    /**
-     * Returns the thumbnail from IFD1 as a bitmap, or null if none exists.
-     *
-     * @return the thumbnail as a bitmap.
-     */
-    public Bitmap getThumbnailBitmap() {
-        if (mData.hasCompressedThumbnail()) {
-            byte[] thumb = mData.getCompressedThumbnail();
-            return BitmapFactory.decodeByteArray(thumb, 0, thumb.length);
-        } else if (mData.hasUncompressedStrip()) {
-            // TODO: implement uncompressed
-        }
-        return null;
-    }
-
-    /**
-     * Returns the thumbnail from IFD1 as a byte array, or null if none exists.
-     * The bytes may either be an uncompressed strip as specified in the exif
-     * standard or a jpeg compressed image.
-     *
-     * @return the thumbnail as a byte array.
-     */
-    public byte[] getThumbnailBytes() {
-        if (mData.hasCompressedThumbnail()) {
-            return mData.getCompressedThumbnail();
-        } else if (mData.hasUncompressedStrip()) {
-            // TODO: implement this
-        }
-        return null;
-    }
-
-    /**
-     * Returns the thumbnail if it is jpeg compressed, or null if none exists.
-     *
-     * @return the thumbnail as a byte array.
-     */
-    public byte[] getThumbnail() {
-        return mData.getCompressedThumbnail();
-    }
-
-    /**
-     * Check if thumbnail is compressed.
-     *
-     * @return true if the thumbnail is compressed.
-     */
-    public boolean isThumbnailCompressed() {
-        return mData.hasCompressedThumbnail();
-    }
-
-    /**
-     * Check if thumbnail exists.
-     *
-     * @return true if a compressed thumbnail exists.
-     */
-    public boolean hasThumbnail() {
-        // TODO: add back in uncompressed strip
-        return mData.hasCompressedThumbnail();
-    }
-
-    // TODO: uncompressed thumbnail setters
-
-    /**
-     * Sets the thumbnail to be a jpeg compressed image. Clears any prior
-     * thumbnail.
-     *
-     * @param thumb a byte array containing a jpeg compressed image.
-     * @return true if the thumbnail was set.
-     */
-    public boolean setCompressedThumbnail(byte[] thumb) {
-        mData.clearThumbnailAndStrips();
-        mData.setCompressedThumbnail(thumb);
-        return true;
-    }
-
-    /**
-     * Sets the thumbnail to be a jpeg compressed bitmap. Clears any prior
-     * thumbnail.
-     *
-     * @param thumb a bitmap to compress to a jpeg thumbnail.
-     * @return true if the thumbnail was set.
-     */
-    public boolean setCompressedThumbnail(Bitmap thumb) {
-        ByteArrayOutputStream thumbnail = new ByteArrayOutputStream();
-        if (!thumb.compress(Bitmap.CompressFormat.JPEG, 90, thumbnail)) {
-            return false;
-        }
-        return setCompressedThumbnail(thumbnail.toByteArray());
-    }
-
-    /**
-     * Clears the compressed thumbnail if it exists.
-     */
-    public void removeCompressedThumbnail() {
-        mData.setCompressedThumbnail(null);
-    }
-
-    // Convenience methods:
-
-    /**
-     * Decodes the user comment tag into string as specified in the EXIF
-     * standard. Returns null if decoding failed.
-     */
-    public String getUserComment() {
-        return mData.getUserComment();
-    }
-
-    /**
-     * Returns the Orientation ExifTag value for a given number of degrees.
-     *
-     * @param degrees the amount an image is rotated in degrees.
-     */
-    public static short getOrientationValueForRotation(int degrees) {
-        degrees %= 360;
-        if (degrees < 0) {
-            degrees += 360;
-        }
-        if (degrees < 90) {
-            return Orientation.TOP_LEFT; // 0 degrees
-        } else if (degrees < 180) {
-            return Orientation.RIGHT_TOP; // 90 degrees cw
-        } else if (degrees < 270) {
-            return Orientation.BOTTOM_LEFT; // 180 degrees
-        } else {
-            return Orientation.RIGHT_BOTTOM; // 270 degrees cw
-        }
-    }
-
-    /**
-     * Returns the rotation degrees corresponding to an ExifTag Orientation
-     * value.
-     *
-     * @param orientation the ExifTag Orientation value.
-     */
-    public static int getRotationForOrientationValue(short orientation) {
-        switch (orientation) {
-            case Orientation.TOP_LEFT:
-                return 0;
-            case Orientation.RIGHT_TOP:
-                return 90;
-            case Orientation.BOTTOM_LEFT:
-                return 180;
-            case Orientation.RIGHT_BOTTOM:
-                return 270;
-            default:
-                return 0;
-        }
-    }
-
-    /**
-     * Gets the double representation of the GPS latitude or longitude
-     * coordinate.
-     *
-     * @param coordinate an array of 3 Rationals representing the degrees,
-     *            minutes, and seconds of the GPS location as defined in the
-     *            exif specification.
-     * @param reference a GPS reference reperesented by a String containing "N",
-     *            "S", "E", or "W".
-     * @return the GPS coordinate represented as degrees + minutes/60 +
-     *         seconds/3600
-     */
-    public static double convertLatOrLongToDouble(Rational[] coordinate, String reference) {
-        try {
-            double degrees = coordinate[0].toDouble();
-            double minutes = coordinate[1].toDouble();
-            double seconds = coordinate[2].toDouble();
-            double result = degrees + minutes / 60.0 + seconds / 3600.0;
-            if ((reference.equals("S") || reference.equals("W"))) {
-                return -result;
-            }
-            return result;
-        } catch (ArrayIndexOutOfBoundsException e) {
-            throw new IllegalArgumentException();
-        }
-    }
-
-    /**
-     * Gets the GPS latitude and longitude as a pair of doubles from this
-     * ExifInterface object's tags, or null if the necessary tags do not exist.
-     *
-     * @return an array of 2 doubles containing the latitude, and longitude
-     *         respectively.
-     * @see #convertLatOrLongToDouble
-     */
-    public double[] getLatLongAsDoubles() {
-        Rational[] latitude = getTagRationalValues(TAG_GPS_LATITUDE);
-        String latitudeRef = getTagStringValue(TAG_GPS_LATITUDE_REF);
-        Rational[] longitude = getTagRationalValues(TAG_GPS_LONGITUDE);
-        String longitudeRef = getTagStringValue(TAG_GPS_LONGITUDE_REF);
-        if (latitude == null || longitude == null || latitudeRef == null || longitudeRef == null
-                || latitude.length < 3 || longitude.length < 3) {
-            return null;
-        }
-        double[] latLon = new double[2];
-        latLon[0] = convertLatOrLongToDouble(latitude, latitudeRef);
-        latLon[1] = convertLatOrLongToDouble(longitude, longitudeRef);
-        return latLon;
-    }
-
-    private static final String GPS_DATE_FORMAT_STR = "yyyy:MM:dd";
-    private static final String DATETIME_FORMAT_STR = "yyyy:MM:dd kk:mm:ss";
-    private final DateFormat mDateTimeStampFormat = new SimpleDateFormat(DATETIME_FORMAT_STR);
-    private final DateFormat mGPSDateStampFormat = new SimpleDateFormat(GPS_DATE_FORMAT_STR);
-    private final Calendar mGPSTimeStampCalendar = Calendar
-            .getInstance(TimeZone.getTimeZone("UTC"));
-
-    /**
-     * Creates, formats, and sets the DateTimeStamp tag for one of:
-     * {@link #TAG_DATE_TIME}, {@link #TAG_DATE_TIME_DIGITIZED},
-     * {@link #TAG_DATE_TIME_ORIGINAL}.
-     *
-     * @param tagId one of the DateTimeStamp tags.
-     * @param timestamp a timestamp to format.
-     * @param timezone a TimeZone object.
-     * @return true if success, false if the tag could not be set.
-     */
-    public boolean addDateTimeStampTag(int tagId, long timestamp, TimeZone timezone) {
-        if (tagId == TAG_DATE_TIME || tagId == TAG_DATE_TIME_DIGITIZED
-                || tagId == TAG_DATE_TIME_ORIGINAL) {
-            mDateTimeStampFormat.setTimeZone(timezone);
-            ExifTag t = buildTag(tagId, mDateTimeStampFormat.format(timestamp));
-            if (t == null) {
-                return false;
-            }
-            setTag(t);
-        } else {
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Creates and sets all to the GPS tags for a give latitude and longitude.
-     *
-     * @param latitude a GPS latitude coordinate.
-     * @param longitude a GPS longitude coordinate.
-     * @return true if success, false if they could not be created or set.
-     */
-    public boolean addGpsTags(double latitude, double longitude) {
-        ExifTag latTag = buildTag(TAG_GPS_LATITUDE, toExifLatLong(latitude));
-        ExifTag longTag = buildTag(TAG_GPS_LONGITUDE, toExifLatLong(longitude));
-        ExifTag latRefTag = buildTag(TAG_GPS_LATITUDE_REF,
-                latitude >= 0 ? ExifInterface.GpsLatitudeRef.NORTH
-                        : ExifInterface.GpsLatitudeRef.SOUTH);
-        ExifTag longRefTag = buildTag(TAG_GPS_LONGITUDE_REF,
-                longitude >= 0 ? ExifInterface.GpsLongitudeRef.EAST
-                        : ExifInterface.GpsLongitudeRef.WEST);
-        if (latTag == null || longTag == null || latRefTag == null || longRefTag == null) {
-            return false;
-        }
-        setTag(latTag);
-        setTag(longTag);
-        setTag(latRefTag);
-        setTag(longRefTag);
-        return true;
-    }
-
-    /**
-     * Creates and sets the GPS timestamp tag.
-     *
-     * @param timestamp a GPS timestamp.
-     * @return true if success, false if could not be created or set.
-     */
-    public boolean addGpsDateTimeStampTag(long timestamp) {
-        ExifTag t = buildTag(TAG_GPS_DATE_STAMP, mGPSDateStampFormat.format(timestamp));
-        if (t == null) {
-            return false;
-        }
-        setTag(t);
-        mGPSTimeStampCalendar.setTimeInMillis(timestamp);
-        t = buildTag(TAG_GPS_TIME_STAMP, new Rational[] {
-                new Rational(mGPSTimeStampCalendar.get(Calendar.HOUR_OF_DAY), 1),
-                new Rational(mGPSTimeStampCalendar.get(Calendar.MINUTE), 1),
-                new Rational(mGPSTimeStampCalendar.get(Calendar.SECOND), 1)
-        });
-        if (t == null) {
-            return false;
-        }
-        setTag(t);
-        return true;
-    }
-
-    private static Rational[] toExifLatLong(double value) {
-        // convert to the format dd/1 mm/1 ssss/100
-        value = Math.abs(value);
-        int degrees = (int) value;
-        value = (value - degrees) * 60;
-        int minutes = (int) value;
-        value = (value - minutes) * 6000;
-        int seconds = (int) value;
-        return new Rational[] {
-                new Rational(degrees, 1), new Rational(minutes, 1), new Rational(seconds, 100)
-        };
-    }
-
-    private void doExifStreamIO(InputStream is, OutputStream os) throws IOException {
-        byte[] buf = new byte[1024];
-        int ret = is.read(buf, 0, 1024);
-        while (ret != -1) {
-            os.write(buf, 0, ret);
-            ret = is.read(buf, 0, 1024);
-        }
-    }
-
-    protected static void closeSilently(Closeable c) {
-        if (c != null) {
-            try {
-                c.close();
-            } catch (Throwable e) {
-                // ignored
-            }
-        }
-    }
-
-    private SparseIntArray mTagInfo = null;
-
-    protected SparseIntArray getTagInfo() {
-        if (mTagInfo == null) {
-            mTagInfo = new SparseIntArray();
-            initTagInfo();
-        }
-        return mTagInfo;
-    }
-
-    private void initTagInfo() {
-        /**
-         * We put tag information in a 4-bytes integer. The first byte a bitmask
-         * representing the allowed IFDs of the tag, the second byte is the data
-         * type, and the last two byte are a short value indicating the default
-         * component count of this tag.
-         */
-        // IFD0 tags
-        int[] ifdAllowedIfds = {
-                IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_1
-        };
-        int ifdFlags = getFlagsFromAllowedIfds(ifdAllowedIfds) << 24;
-        mTagInfo.put(ExifInterface.TAG_MAKE,
-                ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
-        mTagInfo.put(ExifInterface.TAG_IMAGE_WIDTH,
-                ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_IMAGE_LENGTH,
-                ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_BITS_PER_SAMPLE,
-                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 3);
-        mTagInfo.put(ExifInterface.TAG_COMPRESSION,
-                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_PHOTOMETRIC_INTERPRETATION,
-                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_ORIENTATION, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16
-                | 1);
-        mTagInfo.put(ExifInterface.TAG_SAMPLES_PER_PIXEL,
-                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_PLANAR_CONFIGURATION,
-                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_Y_CB_CR_SUB_SAMPLING,
-                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 2);
-        mTagInfo.put(ExifInterface.TAG_Y_CB_CR_POSITIONING,
-                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_X_RESOLUTION,
-                ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_Y_RESOLUTION,
-                ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_RESOLUTION_UNIT,
-                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_STRIP_OFFSETS,
-                ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | ExifTag.SIZE_UNDEFINED);
-        mTagInfo.put(ExifInterface.TAG_ROWS_PER_STRIP,
-                ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_STRIP_BYTE_COUNTS,
-                ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | ExifTag.SIZE_UNDEFINED);
-        mTagInfo.put(ExifInterface.TAG_TRANSFER_FUNCTION,
-                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 3 * 256);
-        mTagInfo.put(ExifInterface.TAG_WHITE_POINT,
-                ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 2);
-        mTagInfo.put(ExifInterface.TAG_PRIMARY_CHROMATICITIES,
-                ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 6);
-        mTagInfo.put(ExifInterface.TAG_Y_CB_CR_COEFFICIENTS,
-                ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 3);
-        mTagInfo.put(ExifInterface.TAG_REFERENCE_BLACK_WHITE,
-                ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 6);
-        mTagInfo.put(ExifInterface.TAG_DATE_TIME,
-                ifdFlags | ExifTag.TYPE_ASCII << 16 | 20);
-        mTagInfo.put(ExifInterface.TAG_IMAGE_DESCRIPTION,
-                ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
-        mTagInfo.put(ExifInterface.TAG_MAKE,
-                ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
-        mTagInfo.put(ExifInterface.TAG_MODEL,
-                ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
-        mTagInfo.put(ExifInterface.TAG_SOFTWARE,
-                ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
-        mTagInfo.put(ExifInterface.TAG_ARTIST,
-                ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
-        mTagInfo.put(ExifInterface.TAG_COPYRIGHT,
-                ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
-        mTagInfo.put(ExifInterface.TAG_EXIF_IFD,
-                ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_GPS_IFD,
-                ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
-        // IFD1 tags
-        int[] ifd1AllowedIfds = {
-            IfdId.TYPE_IFD_1
-        };
-        int ifdFlags1 = getFlagsFromAllowedIfds(ifd1AllowedIfds) << 24;
-        mTagInfo.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT,
-                ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
-                ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
-        // Exif tags
-        int[] exifAllowedIfds = {
-            IfdId.TYPE_IFD_EXIF
-        };
-        int exifFlags = getFlagsFromAllowedIfds(exifAllowedIfds) << 24;
-        mTagInfo.put(ExifInterface.TAG_EXIF_VERSION,
-                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4);
-        mTagInfo.put(ExifInterface.TAG_FLASHPIX_VERSION,
-                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4);
-        mTagInfo.put(ExifInterface.TAG_COLOR_SPACE,
-                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_COMPONENTS_CONFIGURATION,
-                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4);
-        mTagInfo.put(ExifInterface.TAG_COMPRESSED_BITS_PER_PIXEL,
-                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_PIXEL_X_DIMENSION,
-                exifFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_PIXEL_Y_DIMENSION,
-                exifFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_MAKER_NOTE,
-                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
-        mTagInfo.put(ExifInterface.TAG_USER_COMMENT,
-                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
-        mTagInfo.put(ExifInterface.TAG_RELATED_SOUND_FILE,
-                exifFlags | ExifTag.TYPE_ASCII << 16 | 13);
-        mTagInfo.put(ExifInterface.TAG_DATE_TIME_ORIGINAL,
-                exifFlags | ExifTag.TYPE_ASCII << 16 | 20);
-        mTagInfo.put(ExifInterface.TAG_DATE_TIME_DIGITIZED,
-                exifFlags | ExifTag.TYPE_ASCII << 16 | 20);
-        mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME,
-                exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
-        mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME_ORIGINAL,
-                exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
-        mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME_DIGITIZED,
-                exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
-        mTagInfo.put(ExifInterface.TAG_IMAGE_UNIQUE_ID,
-                exifFlags | ExifTag.TYPE_ASCII << 16 | 33);
-        mTagInfo.put(ExifInterface.TAG_EXPOSURE_TIME,
-                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_F_NUMBER,
-                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_EXPOSURE_PROGRAM,
-                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_SPECTRAL_SENSITIVITY,
-                exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
-        mTagInfo.put(ExifInterface.TAG_ISO_SPEED_RATINGS,
-                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | ExifTag.SIZE_UNDEFINED);
-        mTagInfo.put(ExifInterface.TAG_OECF,
-                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
-        mTagInfo.put(ExifInterface.TAG_SHUTTER_SPEED_VALUE,
-                exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_APERTURE_VALUE,
-                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_BRIGHTNESS_VALUE,
-                exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_EXPOSURE_BIAS_VALUE,
-                exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_MAX_APERTURE_VALUE,
-                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_SUBJECT_DISTANCE,
-                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_METERING_MODE,
-                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_LIGHT_SOURCE,
-                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_FLASH,
-                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_FOCAL_LENGTH,
-                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_SUBJECT_AREA,
-                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | ExifTag.SIZE_UNDEFINED);
-        mTagInfo.put(ExifInterface.TAG_FLASH_ENERGY,
-                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE,
-                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
-        mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION,
-                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION,
-                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT,
-                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_SUBJECT_LOCATION,
-                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 2);
-        mTagInfo.put(ExifInterface.TAG_EXPOSURE_INDEX,
-                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_SENSING_METHOD,
-                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_FILE_SOURCE,
-                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_SCENE_TYPE,
-                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_CFA_PATTERN,
-                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
-        mTagInfo.put(ExifInterface.TAG_CUSTOM_RENDERED,
-                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_EXPOSURE_MODE,
-                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_WHITE_BALANCE,
-                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_DIGITAL_ZOOM_RATIO,
-                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_FOCAL_LENGTH_IN_35_MM_FILE,
-                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_SCENE_CAPTURE_TYPE,
-                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_GAIN_CONTROL,
-                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_CONTRAST,
-                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_SATURATION,
-                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_SHARPNESS,
-                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION,
-                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
-        mTagInfo.put(ExifInterface.TAG_SUBJECT_DISTANCE_RANGE,
-                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_INTEROPERABILITY_IFD, exifFlags
-                | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
-        // GPS tag
-        int[] gpsAllowedIfds = {
-            IfdId.TYPE_IFD_GPS
-        };
-        int gpsFlags = getFlagsFromAllowedIfds(gpsAllowedIfds) << 24;
-        mTagInfo.put(ExifInterface.TAG_GPS_VERSION_ID,
-                gpsFlags | ExifTag.TYPE_UNSIGNED_BYTE << 16 | 4);
-        mTagInfo.put(ExifInterface.TAG_GPS_LATITUDE_REF,
-                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
-        mTagInfo.put(ExifInterface.TAG_GPS_LONGITUDE_REF,
-                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
-        mTagInfo.put(ExifInterface.TAG_GPS_LATITUDE,
-                gpsFlags | ExifTag.TYPE_RATIONAL << 16 | 3);
-        mTagInfo.put(ExifInterface.TAG_GPS_LONGITUDE,
-                gpsFlags | ExifTag.TYPE_RATIONAL << 16 | 3);
-        mTagInfo.put(ExifInterface.TAG_GPS_ALTITUDE_REF,
-                gpsFlags | ExifTag.TYPE_UNSIGNED_BYTE << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_GPS_ALTITUDE,
-                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_GPS_TIME_STAMP,
-                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 3);
-        mTagInfo.put(ExifInterface.TAG_GPS_SATTELLITES,
-                gpsFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
-        mTagInfo.put(ExifInterface.TAG_GPS_STATUS,
-                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
-        mTagInfo.put(ExifInterface.TAG_GPS_MEASURE_MODE,
-                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
-        mTagInfo.put(ExifInterface.TAG_GPS_DOP,
-                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_GPS_SPEED_REF,
-                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
-        mTagInfo.put(ExifInterface.TAG_GPS_SPEED,
-                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_GPS_TRACK_REF,
-                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
-        mTagInfo.put(ExifInterface.TAG_GPS_TRACK,
-                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_GPS_IMG_DIRECTION_REF,
-                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
-        mTagInfo.put(ExifInterface.TAG_GPS_IMG_DIRECTION,
-                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_GPS_MAP_DATUM,
-                gpsFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
-        mTagInfo.put(ExifInterface.TAG_GPS_DEST_LATITUDE_REF,
-                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
-        mTagInfo.put(ExifInterface.TAG_GPS_DEST_LATITUDE,
-                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_GPS_DEST_BEARING_REF,
-                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
-        mTagInfo.put(ExifInterface.TAG_GPS_DEST_BEARING,
-                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_GPS_DEST_DISTANCE_REF,
-                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
-        mTagInfo.put(ExifInterface.TAG_GPS_DEST_DISTANCE,
-                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        mTagInfo.put(ExifInterface.TAG_GPS_PROCESSING_METHOD,
-                gpsFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
-        mTagInfo.put(ExifInterface.TAG_GPS_AREA_INFORMATION,
-                gpsFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
-        mTagInfo.put(ExifInterface.TAG_GPS_DATE_STAMP,
-                gpsFlags | ExifTag.TYPE_ASCII << 16 | 11);
-        mTagInfo.put(ExifInterface.TAG_GPS_DIFFERENTIAL,
-                gpsFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 11);
-        // Interoperability tag
-        int[] interopAllowedIfds = {
-            IfdId.TYPE_IFD_INTEROPERABILITY
-        };
-        int interopFlags = getFlagsFromAllowedIfds(interopAllowedIfds) << 24;
-        mTagInfo.put(TAG_INTEROPERABILITY_INDEX, interopFlags | ExifTag.TYPE_ASCII << 16
-                | ExifTag.SIZE_UNDEFINED);
-    }
-
-    protected static int getAllowedIfdFlagsFromInfo(int info) {
-        return info >>> 24;
-    }
-
-    protected static int[] getAllowedIfdsFromInfo(int info) {
-        int ifdFlags = getAllowedIfdFlagsFromInfo(info);
-        int[] ifds = IfdData.getIfds();
-        ArrayList<Integer> l = new ArrayList<Integer>();
-        for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
-            int flag = (ifdFlags >> i) & 1;
-            if (flag == 1) {
-                l.add(ifds[i]);
-            }
-        }
-        if (l.size() <= 0) {
-            return null;
-        }
-        int[] ret = new int[l.size()];
-        int j = 0;
-        for (int i : l) {
-            ret[j++] = i;
-        }
-        return ret;
-    }
-
-    protected static boolean isIfdAllowed(int info, int ifd) {
-        int[] ifds = IfdData.getIfds();
-        int ifdFlags = getAllowedIfdFlagsFromInfo(info);
-        for (int i = 0; i < ifds.length; i++) {
-            if (ifd == ifds[i] && ((ifdFlags >> i) & 1) == 1) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    protected static int getFlagsFromAllowedIfds(int[] allowedIfds) {
-        if (allowedIfds == null || allowedIfds.length == 0) {
-            return 0;
-        }
-        int flags = 0;
-        int[] ifds = IfdData.getIfds();
-        for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
-            for (int j : allowedIfds) {
-                if (ifds[i] == j) {
-                    flags |= 1 << i;
-                    break;
-                }
-            }
-        }
-        return flags;
-    }
-
-    protected static short getTypeFromInfo(int info) {
-        return (short) ((info >> 16) & 0x0ff);
-    }
-
-    protected static int getComponentCountFromInfo(int info) {
-        return info & 0x0ffff;
-    }
-
-}
diff --git a/WallpaperPicker/src/com/android/gallery3d/exif/ExifInvalidFormatException.java b/WallpaperPicker/src/com/android/gallery3d/exif/ExifInvalidFormatException.java
deleted file mode 100644
index bf923ec..0000000
--- a/WallpaperPicker/src/com/android/gallery3d/exif/ExifInvalidFormatException.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2012 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.exif;
-
-public class ExifInvalidFormatException extends Exception {
-    public ExifInvalidFormatException(String meg) {
-        super(meg);
-    }
-}
\ No newline at end of file
diff --git a/WallpaperPicker/src/com/android/gallery3d/exif/ExifModifier.java b/WallpaperPicker/src/com/android/gallery3d/exif/ExifModifier.java
deleted file mode 100644
index 0531cba..0000000
--- a/WallpaperPicker/src/com/android/gallery3d/exif/ExifModifier.java
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * Copyright (C) 2012 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.exif;
-
-import android.util.Log;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.ArrayList;
-import java.util.List;
-
-class ExifModifier {
-    public static final String TAG = "ExifModifier";
-    public static final boolean DEBUG = false;
-    private final ByteBuffer mByteBuffer;
-    private final ExifData mTagToModified;
-    private final List<TagOffset> mTagOffsets = new ArrayList<TagOffset>();
-    private final ExifInterface mInterface;
-    private int mOffsetBase;
-
-    private static class TagOffset {
-        final int mOffset;
-        final ExifTag mTag;
-
-        TagOffset(ExifTag tag, int offset) {
-            mTag = tag;
-            mOffset = offset;
-        }
-    }
-
-    protected ExifModifier(ByteBuffer byteBuffer, ExifInterface iRef) throws IOException,
-            ExifInvalidFormatException {
-        mByteBuffer = byteBuffer;
-        mOffsetBase = byteBuffer.position();
-        mInterface = iRef;
-        InputStream is = null;
-        try {
-            is = new ByteBufferInputStream(byteBuffer);
-            // Do not require any IFD;
-            ExifParser parser = ExifParser.parse(is, mInterface);
-            mTagToModified = new ExifData(parser.getByteOrder());
-            mOffsetBase += parser.getTiffStartPosition();
-            mByteBuffer.position(0);
-        } finally {
-            ExifInterface.closeSilently(is);
-        }
-    }
-
-    protected ByteOrder getByteOrder() {
-        return mTagToModified.getByteOrder();
-    }
-
-    protected boolean commit() throws IOException, ExifInvalidFormatException {
-        InputStream is = null;
-        try {
-            is = new ByteBufferInputStream(mByteBuffer);
-            int flag = 0;
-            IfdData[] ifdDatas = new IfdData[] {
-                    mTagToModified.getIfdData(IfdId.TYPE_IFD_0),
-                    mTagToModified.getIfdData(IfdId.TYPE_IFD_1),
-                    mTagToModified.getIfdData(IfdId.TYPE_IFD_EXIF),
-                    mTagToModified.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY),
-                    mTagToModified.getIfdData(IfdId.TYPE_IFD_GPS)
-            };
-
-            if (ifdDatas[IfdId.TYPE_IFD_0] != null) {
-                flag |= ExifParser.OPTION_IFD_0;
-            }
-            if (ifdDatas[IfdId.TYPE_IFD_1] != null) {
-                flag |= ExifParser.OPTION_IFD_1;
-            }
-            if (ifdDatas[IfdId.TYPE_IFD_EXIF] != null) {
-                flag |= ExifParser.OPTION_IFD_EXIF;
-            }
-            if (ifdDatas[IfdId.TYPE_IFD_GPS] != null) {
-                flag |= ExifParser.OPTION_IFD_GPS;
-            }
-            if (ifdDatas[IfdId.TYPE_IFD_INTEROPERABILITY] != null) {
-                flag |= ExifParser.OPTION_IFD_INTEROPERABILITY;
-            }
-
-            ExifParser parser = ExifParser.parse(is, flag, mInterface);
-            int event = parser.next();
-            IfdData currIfd = null;
-            while (event != ExifParser.EVENT_END) {
-                switch (event) {
-                    case ExifParser.EVENT_START_OF_IFD:
-                        currIfd = ifdDatas[parser.getCurrentIfd()];
-                        if (currIfd == null) {
-                            parser.skipRemainingTagsInCurrentIfd();
-                        }
-                        break;
-                    case ExifParser.EVENT_NEW_TAG:
-                        ExifTag oldTag = parser.getTag();
-                        ExifTag newTag = currIfd.getTag(oldTag.getTagId());
-                        if (newTag != null) {
-                            if (newTag.getComponentCount() != oldTag.getComponentCount()
-                                    || newTag.getDataType() != oldTag.getDataType()) {
-                                return false;
-                            } else {
-                                mTagOffsets.add(new TagOffset(newTag, oldTag.getOffset()));
-                                currIfd.removeTag(oldTag.getTagId());
-                                if (currIfd.getTagCount() == 0) {
-                                    parser.skipRemainingTagsInCurrentIfd();
-                                }
-                            }
-                        }
-                        break;
-                }
-                event = parser.next();
-            }
-            for (IfdData ifd : ifdDatas) {
-                if (ifd != null && ifd.getTagCount() > 0) {
-                    return false;
-                }
-            }
-            modify();
-        } finally {
-            ExifInterface.closeSilently(is);
-        }
-        return true;
-    }
-
-    private void modify() {
-        mByteBuffer.order(getByteOrder());
-        for (TagOffset tagOffset : mTagOffsets) {
-            writeTagValue(tagOffset.mTag, tagOffset.mOffset);
-        }
-    }
-
-    private void writeTagValue(ExifTag tag, int offset) {
-        if (DEBUG) {
-            Log.v(TAG, "modifying tag to: \n" + tag.toString());
-            Log.v(TAG, "at offset: " + offset);
-        }
-        mByteBuffer.position(offset + mOffsetBase);
-        switch (tag.getDataType()) {
-            case ExifTag.TYPE_ASCII:
-                byte buf[] = tag.getStringByte();
-                if (buf.length == tag.getComponentCount()) {
-                    buf[buf.length - 1] = 0;
-                    mByteBuffer.put(buf);
-                } else {
-                    mByteBuffer.put(buf);
-                    mByteBuffer.put((byte) 0);
-                }
-                break;
-            case ExifTag.TYPE_LONG:
-            case ExifTag.TYPE_UNSIGNED_LONG:
-                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
-                    mByteBuffer.putInt((int) tag.getValueAt(i));
-                }
-                break;
-            case ExifTag.TYPE_RATIONAL:
-            case ExifTag.TYPE_UNSIGNED_RATIONAL:
-                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
-                    Rational v = tag.getRational(i);
-                    mByteBuffer.putInt((int) v.getNumerator());
-                    mByteBuffer.putInt((int) v.getDenominator());
-                }
-                break;
-            case ExifTag.TYPE_UNDEFINED:
-            case ExifTag.TYPE_UNSIGNED_BYTE:
-                buf = new byte[tag.getComponentCount()];
-                tag.getBytes(buf);
-                mByteBuffer.put(buf);
-                break;
-            case ExifTag.TYPE_UNSIGNED_SHORT:
-                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
-                    mByteBuffer.putShort((short) tag.getValueAt(i));
-                }
-                break;
-        }
-    }
-
-    public void modifyTag(ExifTag tag) {
-        mTagToModified.addTag(tag);
-    }
-}
diff --git a/WallpaperPicker/src/com/android/gallery3d/exif/ExifOutputStream.java b/WallpaperPicker/src/com/android/gallery3d/exif/ExifOutputStream.java
deleted file mode 100644
index 7ca05f2..0000000
--- a/WallpaperPicker/src/com/android/gallery3d/exif/ExifOutputStream.java
+++ /dev/null
@@ -1,518 +0,0 @@
-/*
- * Copyright (C) 2012 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.exif;
-
-import android.util.Log;
-
-import java.io.BufferedOutputStream;
-import java.io.FilterOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.ArrayList;
-
-/**
- * This class provides a way to replace the Exif header of a JPEG image.
- * <p>
- * Below is an example of writing EXIF data into a file
- *
- * <pre>
- * public static void writeExif(byte[] jpeg, ExifData exif, String path) {
- *     OutputStream os = null;
- *     try {
- *         os = new FileOutputStream(path);
- *         ExifOutputStream eos = new ExifOutputStream(os);
- *         // Set the exif header
- *         eos.setExifData(exif);
- *         // Write the original jpeg out, the header will be add into the file.
- *         eos.write(jpeg);
- *     } catch (FileNotFoundException e) {
- *         e.printStackTrace();
- *     } catch (IOException e) {
- *         e.printStackTrace();
- *     } finally {
- *         if (os != null) {
- *             try {
- *                 os.close();
- *             } catch (IOException e) {
- *                 e.printStackTrace();
- *             }
- *         }
- *     }
- * }
- * </pre>
- */
-class ExifOutputStream extends FilterOutputStream {
-    private static final String TAG = "ExifOutputStream";
-    private static final boolean DEBUG = false;
-    private static final int STREAMBUFFER_SIZE = 0x00010000; // 64Kb
-
-    private static final int STATE_SOI = 0;
-    private static final int STATE_FRAME_HEADER = 1;
-    private static final int STATE_JPEG_DATA = 2;
-
-    private static final int EXIF_HEADER = 0x45786966;
-    private static final short TIFF_HEADER = 0x002A;
-    private static final short TIFF_BIG_ENDIAN = 0x4d4d;
-    private static final short TIFF_LITTLE_ENDIAN = 0x4949;
-    private static final short TAG_SIZE = 12;
-    private static final short TIFF_HEADER_SIZE = 8;
-    private static final int MAX_EXIF_SIZE = 65535;
-
-    private ExifData mExifData;
-    private int mState = STATE_SOI;
-    private int mByteToSkip;
-    private int mByteToCopy;
-    private byte[] mSingleByteArray = new byte[1];
-    private ByteBuffer mBuffer = ByteBuffer.allocate(4);
-    private final ExifInterface mInterface;
-
-    protected ExifOutputStream(OutputStream ou, ExifInterface iRef) {
-        super(new BufferedOutputStream(ou, STREAMBUFFER_SIZE));
-        mInterface = iRef;
-    }
-
-    /**
-     * Sets the ExifData to be written into the JPEG file. Should be called
-     * before writing image data.
-     */
-    protected void setExifData(ExifData exifData) {
-        mExifData = exifData;
-    }
-
-    /**
-     * Gets the Exif header to be written into the JPEF file.
-     */
-    protected ExifData getExifData() {
-        return mExifData;
-    }
-
-    private int requestByteToBuffer(int requestByteCount, byte[] buffer
-            , int offset, int length) {
-        int byteNeeded = requestByteCount - mBuffer.position();
-        int byteToRead = length > byteNeeded ? byteNeeded : length;
-        mBuffer.put(buffer, offset, byteToRead);
-        return byteToRead;
-    }
-
-    /**
-     * Writes the image out. The input data should be a valid JPEG format. After
-     * writing, it's Exif header will be replaced by the given header.
-     */
-    @Override
-    public void write(byte[] buffer, int offset, int length) throws IOException {
-        while ((mByteToSkip > 0 || mByteToCopy > 0 || mState != STATE_JPEG_DATA)
-                && length > 0) {
-            if (mByteToSkip > 0) {
-                int byteToProcess = length > mByteToSkip ? mByteToSkip : length;
-                length -= byteToProcess;
-                mByteToSkip -= byteToProcess;
-                offset += byteToProcess;
-            }
-            if (mByteToCopy > 0) {
-                int byteToProcess = length > mByteToCopy ? mByteToCopy : length;
-                out.write(buffer, offset, byteToProcess);
-                length -= byteToProcess;
-                mByteToCopy -= byteToProcess;
-                offset += byteToProcess;
-            }
-            if (length == 0) {
-                return;
-            }
-            switch (mState) {
-                case STATE_SOI:
-                    int byteRead = requestByteToBuffer(2, buffer, offset, length);
-                    offset += byteRead;
-                    length -= byteRead;
-                    if (mBuffer.position() < 2) {
-                        return;
-                    }
-                    mBuffer.rewind();
-                    if (mBuffer.getShort() != JpegHeader.SOI) {
-                        throw new IOException("Not a valid jpeg image, cannot write exif");
-                    }
-                    out.write(mBuffer.array(), 0, 2);
-                    mState = STATE_FRAME_HEADER;
-                    mBuffer.rewind();
-                    writeExifData();
-                    break;
-                case STATE_FRAME_HEADER:
-                    // We ignore the APP1 segment and copy all other segments
-                    // until SOF tag.
-                    byteRead = requestByteToBuffer(4, buffer, offset, length);
-                    offset += byteRead;
-                    length -= byteRead;
-                    // Check if this image data doesn't contain SOF.
-                    if (mBuffer.position() == 2) {
-                        short tag = mBuffer.getShort();
-                        if (tag == JpegHeader.EOI) {
-                            out.write(mBuffer.array(), 0, 2);
-                            mBuffer.rewind();
-                        }
-                    }
-                    if (mBuffer.position() < 4) {
-                        return;
-                    }
-                    mBuffer.rewind();
-                    short marker = mBuffer.getShort();
-                    if (marker == JpegHeader.APP1) {
-                        mByteToSkip = (mBuffer.getShort() & 0x0000ffff) - 2;
-                        mState = STATE_JPEG_DATA;
-                    } else if (!JpegHeader.isSofMarker(marker)) {
-                        out.write(mBuffer.array(), 0, 4);
-                        mByteToCopy = (mBuffer.getShort() & 0x0000ffff) - 2;
-                    } else {
-                        out.write(mBuffer.array(), 0, 4);
-                        mState = STATE_JPEG_DATA;
-                    }
-                    mBuffer.rewind();
-            }
-        }
-        if (length > 0) {
-            out.write(buffer, offset, length);
-        }
-    }
-
-    /**
-     * Writes the one bytes out. The input data should be a valid JPEG format.
-     * After writing, it's Exif header will be replaced by the given header.
-     */
-    @Override
-    public void write(int oneByte) throws IOException {
-        mSingleByteArray[0] = (byte) (0xff & oneByte);
-        write(mSingleByteArray);
-    }
-
-    /**
-     * Equivalent to calling write(buffer, 0, buffer.length).
-     */
-    @Override
-    public void write(byte[] buffer) throws IOException {
-        write(buffer, 0, buffer.length);
-    }
-
-    private void writeExifData() throws IOException {
-        if (mExifData == null) {
-            return;
-        }
-        if (DEBUG) {
-            Log.v(TAG, "Writing exif data...");
-        }
-        ArrayList<ExifTag> nullTags = stripNullValueTags(mExifData);
-        createRequiredIfdAndTag();
-        int exifSize = calculateAllOffset();
-        if (exifSize + 8 > MAX_EXIF_SIZE) {
-            throw new IOException("Exif header is too large (>64Kb)");
-        }
-        OrderedDataOutputStream dataOutputStream = new OrderedDataOutputStream(out);
-        dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN);
-        dataOutputStream.writeShort(JpegHeader.APP1);
-        dataOutputStream.writeShort((short) (exifSize + 8));
-        dataOutputStream.writeInt(EXIF_HEADER);
-        dataOutputStream.writeShort((short) 0x0000);
-        if (mExifData.getByteOrder() == ByteOrder.BIG_ENDIAN) {
-            dataOutputStream.writeShort(TIFF_BIG_ENDIAN);
-        } else {
-            dataOutputStream.writeShort(TIFF_LITTLE_ENDIAN);
-        }
-        dataOutputStream.setByteOrder(mExifData.getByteOrder());
-        dataOutputStream.writeShort(TIFF_HEADER);
-        dataOutputStream.writeInt(8);
-        writeAllTags(dataOutputStream);
-        writeThumbnail(dataOutputStream);
-        for (ExifTag t : nullTags) {
-            mExifData.addTag(t);
-        }
-    }
-
-    private ArrayList<ExifTag> stripNullValueTags(ExifData data) {
-        ArrayList<ExifTag> nullTags = new ArrayList<ExifTag>();
-        for(ExifTag t : data.getAllTags()) {
-            if (t.getValue() == null && !ExifInterface.isOffsetTag(t.getTagId())) {
-                data.removeTag(t.getTagId(), t.getIfd());
-                nullTags.add(t);
-            }
-        }
-        return nullTags;
-    }
-
-    private void writeThumbnail(OrderedDataOutputStream dataOutputStream) throws IOException {
-        if (mExifData.hasCompressedThumbnail()) {
-            dataOutputStream.write(mExifData.getCompressedThumbnail());
-        } else if (mExifData.hasUncompressedStrip()) {
-            for (int i = 0; i < mExifData.getStripCount(); i++) {
-                dataOutputStream.write(mExifData.getStrip(i));
-            }
-        }
-    }
-
-    private void writeAllTags(OrderedDataOutputStream dataOutputStream) throws IOException {
-        writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_0), dataOutputStream);
-        writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_EXIF), dataOutputStream);
-        IfdData interoperabilityIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
-        if (interoperabilityIfd != null) {
-            writeIfd(interoperabilityIfd, dataOutputStream);
-        }
-        IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
-        if (gpsIfd != null) {
-            writeIfd(gpsIfd, dataOutputStream);
-        }
-        IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
-        if (ifd1 != null) {
-            writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_1), dataOutputStream);
-        }
-    }
-
-    private void writeIfd(IfdData ifd, OrderedDataOutputStream dataOutputStream)
-            throws IOException {
-        ExifTag[] tags = ifd.getAllTags();
-        dataOutputStream.writeShort((short) tags.length);
-        for (ExifTag tag : tags) {
-            dataOutputStream.writeShort(tag.getTagId());
-            dataOutputStream.writeShort(tag.getDataType());
-            dataOutputStream.writeInt(tag.getComponentCount());
-            if (DEBUG) {
-                Log.v(TAG, "\n" + tag.toString());
-            }
-            if (tag.getDataSize() > 4) {
-                dataOutputStream.writeInt(tag.getOffset());
-            } else {
-                ExifOutputStream.writeTagValue(tag, dataOutputStream);
-                for (int i = 0, n = 4 - tag.getDataSize(); i < n; i++) {
-                    dataOutputStream.write(0);
-                }
-            }
-        }
-        dataOutputStream.writeInt(ifd.getOffsetToNextIfd());
-        for (ExifTag tag : tags) {
-            if (tag.getDataSize() > 4) {
-                ExifOutputStream.writeTagValue(tag, dataOutputStream);
-            }
-        }
-    }
-
-    private int calculateOffsetOfIfd(IfdData ifd, int offset) {
-        offset += 2 + ifd.getTagCount() * TAG_SIZE + 4;
-        ExifTag[] tags = ifd.getAllTags();
-        for (ExifTag tag : tags) {
-            if (tag.getDataSize() > 4) {
-                tag.setOffset(offset);
-                offset += tag.getDataSize();
-            }
-        }
-        return offset;
-    }
-
-    private void createRequiredIfdAndTag() throws IOException {
-        // IFD0 is required for all file
-        IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
-        if (ifd0 == null) {
-            ifd0 = new IfdData(IfdId.TYPE_IFD_0);
-            mExifData.addIfdData(ifd0);
-        }
-        ExifTag exifOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_EXIF_IFD);
-        if (exifOffsetTag == null) {
-            throw new IOException("No definition for crucial exif tag: "
-                    + ExifInterface.TAG_EXIF_IFD);
-        }
-        ifd0.setTag(exifOffsetTag);
-
-        // Exif IFD is required for all files.
-        IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF);
-        if (exifIfd == null) {
-            exifIfd = new IfdData(IfdId.TYPE_IFD_EXIF);
-            mExifData.addIfdData(exifIfd);
-        }
-
-        // GPS IFD
-        IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
-        if (gpsIfd != null) {
-            ExifTag gpsOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_GPS_IFD);
-            if (gpsOffsetTag == null) {
-                throw new IOException("No definition for crucial exif tag: "
-                        + ExifInterface.TAG_GPS_IFD);
-            }
-            ifd0.setTag(gpsOffsetTag);
-        }
-
-        // Interoperability IFD
-        IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
-        if (interIfd != null) {
-            ExifTag interOffsetTag = mInterface
-                    .buildUninitializedTag(ExifInterface.TAG_INTEROPERABILITY_IFD);
-            if (interOffsetTag == null) {
-                throw new IOException("No definition for crucial exif tag: "
-                        + ExifInterface.TAG_INTEROPERABILITY_IFD);
-            }
-            exifIfd.setTag(interOffsetTag);
-        }
-
-        IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
-
-        // thumbnail
-        if (mExifData.hasCompressedThumbnail()) {
-
-            if (ifd1 == null) {
-                ifd1 = new IfdData(IfdId.TYPE_IFD_1);
-                mExifData.addIfdData(ifd1);
-            }
-
-            ExifTag offsetTag = mInterface
-                    .buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
-            if (offsetTag == null) {
-                throw new IOException("No definition for crucial exif tag: "
-                        + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
-            }
-
-            ifd1.setTag(offsetTag);
-            ExifTag lengthTag = mInterface
-                    .buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
-            if (lengthTag == null) {
-                throw new IOException("No definition for crucial exif tag: "
-                        + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
-            }
-
-            lengthTag.setValue(mExifData.getCompressedThumbnail().length);
-            ifd1.setTag(lengthTag);
-
-            // Get rid of tags for uncompressed if they exist.
-            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS));
-            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS));
-        } else if (mExifData.hasUncompressedStrip()) {
-            if (ifd1 == null) {
-                ifd1 = new IfdData(IfdId.TYPE_IFD_1);
-                mExifData.addIfdData(ifd1);
-            }
-            int stripCount = mExifData.getStripCount();
-            ExifTag offsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_STRIP_OFFSETS);
-            if (offsetTag == null) {
-                throw new IOException("No definition for crucial exif tag: "
-                        + ExifInterface.TAG_STRIP_OFFSETS);
-            }
-            ExifTag lengthTag = mInterface
-                    .buildUninitializedTag(ExifInterface.TAG_STRIP_BYTE_COUNTS);
-            if (lengthTag == null) {
-                throw new IOException("No definition for crucial exif tag: "
-                        + ExifInterface.TAG_STRIP_BYTE_COUNTS);
-            }
-            long[] lengths = new long[stripCount];
-            for (int i = 0; i < mExifData.getStripCount(); i++) {
-                lengths[i] = mExifData.getStrip(i).length;
-            }
-            lengthTag.setValue(lengths);
-            ifd1.setTag(offsetTag);
-            ifd1.setTag(lengthTag);
-            // Get rid of tags for compressed if they exist.
-            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT));
-            ifd1.removeTag(ExifInterface
-                    .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
-        } else if (ifd1 != null) {
-            // Get rid of offset and length tags if there is no thumbnail.
-            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS));
-            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS));
-            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT));
-            ifd1.removeTag(ExifInterface
-                    .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
-        }
-    }
-
-    private int calculateAllOffset() {
-        int offset = TIFF_HEADER_SIZE;
-        IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
-        offset = calculateOffsetOfIfd(ifd0, offset);
-        ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_EXIF_IFD)).setValue(offset);
-
-        IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF);
-        offset = calculateOffsetOfIfd(exifIfd, offset);
-
-        IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
-        if (interIfd != null) {
-            exifIfd.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD))
-                    .setValue(offset);
-            offset = calculateOffsetOfIfd(interIfd, offset);
-        }
-
-        IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
-        if (gpsIfd != null) {
-            ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD)).setValue(offset);
-            offset = calculateOffsetOfIfd(gpsIfd, offset);
-        }
-
-        IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
-        if (ifd1 != null) {
-            ifd0.setOffsetToNextIfd(offset);
-            offset = calculateOffsetOfIfd(ifd1, offset);
-        }
-
-        // thumbnail
-        if (mExifData.hasCompressedThumbnail()) {
-            ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT))
-                    .setValue(offset);
-            offset += mExifData.getCompressedThumbnail().length;
-        } else if (mExifData.hasUncompressedStrip()) {
-            int stripCount = mExifData.getStripCount();
-            long[] offsets = new long[stripCount];
-            for (int i = 0; i < mExifData.getStripCount(); i++) {
-                offsets[i] = offset;
-                offset += mExifData.getStrip(i).length;
-            }
-            ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS)).setValue(
-                    offsets);
-        }
-        return offset;
-    }
-
-    static void writeTagValue(ExifTag tag, OrderedDataOutputStream dataOutputStream)
-            throws IOException {
-        switch (tag.getDataType()) {
-            case ExifTag.TYPE_ASCII:
-                byte buf[] = tag.getStringByte();
-                if (buf.length == tag.getComponentCount()) {
-                    buf[buf.length - 1] = 0;
-                    dataOutputStream.write(buf);
-                } else {
-                    dataOutputStream.write(buf);
-                    dataOutputStream.write(0);
-                }
-                break;
-            case ExifTag.TYPE_LONG:
-            case ExifTag.TYPE_UNSIGNED_LONG:
-                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
-                    dataOutputStream.writeInt((int) tag.getValueAt(i));
-                }
-                break;
-            case ExifTag.TYPE_RATIONAL:
-            case ExifTag.TYPE_UNSIGNED_RATIONAL:
-                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
-                    dataOutputStream.writeRational(tag.getRational(i));
-                }
-                break;
-            case ExifTag.TYPE_UNDEFINED:
-            case ExifTag.TYPE_UNSIGNED_BYTE:
-                buf = new byte[tag.getComponentCount()];
-                tag.getBytes(buf);
-                dataOutputStream.write(buf);
-                break;
-            case ExifTag.TYPE_UNSIGNED_SHORT:
-                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
-                    dataOutputStream.writeShort((short) tag.getValueAt(i));
-                }
-                break;
-        }
-    }
-}
diff --git a/WallpaperPicker/src/com/android/gallery3d/exif/ExifParser.java b/WallpaperPicker/src/com/android/gallery3d/exif/ExifParser.java
deleted file mode 100644
index 5467d42..0000000
--- a/WallpaperPicker/src/com/android/gallery3d/exif/ExifParser.java
+++ /dev/null
@@ -1,916 +0,0 @@
-/*
- * Copyright (C) 2012 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.exif;
-
-import android.util.Log;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteOrder;
-import java.nio.charset.Charset;
-import java.util.Map.Entry;
-import java.util.TreeMap;
-
-/**
- * This class provides a low-level EXIF parsing API. Given a JPEG format
- * InputStream, the caller can request which IFD's to read via
- * {@link #parse(InputStream, int)} with given options.
- * <p>
- * Below is an example of getting EXIF data from IFD 0 and EXIF IFD using the
- * parser.
- *
- * <pre>
- * void parse() {
- *     ExifParser parser = ExifParser.parse(mImageInputStream,
- *             ExifParser.OPTION_IFD_0 | ExifParser.OPTIONS_IFD_EXIF);
- *     int event = parser.next();
- *     while (event != ExifParser.EVENT_END) {
- *         switch (event) {
- *             case ExifParser.EVENT_START_OF_IFD:
- *                 break;
- *             case ExifParser.EVENT_NEW_TAG:
- *                 ExifTag tag = parser.getTag();
- *                 if (!tag.hasValue()) {
- *                     parser.registerForTagValue(tag);
- *                 } else {
- *                     processTag(tag);
- *                 }
- *                 break;
- *             case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
- *                 tag = parser.getTag();
- *                 if (tag.getDataType() != ExifTag.TYPE_UNDEFINED) {
- *                     processTag(tag);
- *                 }
- *                 break;
- *         }
- *         event = parser.next();
- *     }
- * }
- *
- * void processTag(ExifTag tag) {
- *     // process the tag as you like.
- * }
- * </pre>
- */
-class ExifParser {
-    private static final boolean LOGV = false;
-    private static final String TAG = "ExifParser";
-    /**
-     * When the parser reaches a new IFD area. Call {@link #getCurrentIfd()} to
-     * know which IFD we are in.
-     */
-    public static final int EVENT_START_OF_IFD = 0;
-    /**
-     * When the parser reaches a new tag. Call {@link #getTag()}to get the
-     * corresponding tag.
-     */
-    public static final int EVENT_NEW_TAG = 1;
-    /**
-     * When the parser reaches the value area of tag that is registered by
-     * {@link #registerForTagValue(ExifTag)} previously. Call {@link #getTag()}
-     * to get the corresponding tag.
-     */
-    public static final int EVENT_VALUE_OF_REGISTERED_TAG = 2;
-
-    /**
-     * When the parser reaches the compressed image area.
-     */
-    public static final int EVENT_COMPRESSED_IMAGE = 3;
-    /**
-     * When the parser reaches the uncompressed image strip. Call
-     * {@link #getStripIndex()} to get the index of the strip.
-     *
-     * @see #getStripIndex()
-     * @see #getStripCount()
-     */
-    public static final int EVENT_UNCOMPRESSED_STRIP = 4;
-    /**
-     * When there is nothing more to parse.
-     */
-    public static final int EVENT_END = 5;
-
-    /**
-     * Option bit to request to parse IFD0.
-     */
-    public static final int OPTION_IFD_0 = 1 << 0;
-    /**
-     * Option bit to request to parse IFD1.
-     */
-    public static final int OPTION_IFD_1 = 1 << 1;
-    /**
-     * Option bit to request to parse Exif-IFD.
-     */
-    public static final int OPTION_IFD_EXIF = 1 << 2;
-    /**
-     * Option bit to request to parse GPS-IFD.
-     */
-    public static final int OPTION_IFD_GPS = 1 << 3;
-    /**
-     * Option bit to request to parse Interoperability-IFD.
-     */
-    public static final int OPTION_IFD_INTEROPERABILITY = 1 << 4;
-    /**
-     * Option bit to request to parse thumbnail.
-     */
-    public static final int OPTION_THUMBNAIL = 1 << 5;
-
-    protected static final int EXIF_HEADER = 0x45786966; // EXIF header "Exif"
-    protected static final short EXIF_HEADER_TAIL = (short) 0x0000; // EXIF header in APP1
-
-    // TIFF header
-    protected static final short LITTLE_ENDIAN_TAG = (short) 0x4949; // "II"
-    protected static final short BIG_ENDIAN_TAG = (short) 0x4d4d; // "MM"
-    protected static final short TIFF_HEADER_TAIL = 0x002A;
-
-    protected static final int TAG_SIZE = 12;
-    protected static final int OFFSET_SIZE = 2;
-
-    private static final Charset US_ASCII = Charset.forName("US-ASCII");
-
-    protected static final int DEFAULT_IFD0_OFFSET = 8;
-
-    private final CountedDataInputStream mTiffStream;
-    private final int mOptions;
-    private int mIfdStartOffset = 0;
-    private int mNumOfTagInIfd = 0;
-    private int mIfdType;
-    private ExifTag mTag;
-    private ImageEvent mImageEvent;
-    private int mStripCount;
-    private ExifTag mStripSizeTag;
-    private ExifTag mJpegSizeTag;
-    private boolean mNeedToParseOffsetsInCurrentIfd;
-    private boolean mContainExifData = false;
-    private int mApp1End;
-    private int mOffsetToApp1EndFromSOF = 0;
-    private byte[] mDataAboveIfd0;
-    private int mIfd0Position;
-    private int mTiffStartPosition;
-    private final ExifInterface mInterface;
-
-    private static final short TAG_EXIF_IFD = ExifInterface
-            .getTrueTagKey(ExifInterface.TAG_EXIF_IFD);
-    private static final short TAG_GPS_IFD = ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD);
-    private static final short TAG_INTEROPERABILITY_IFD = ExifInterface
-            .getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD);
-    private static final short TAG_JPEG_INTERCHANGE_FORMAT = ExifInterface
-            .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
-    private static final short TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = ExifInterface
-            .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
-    private static final short TAG_STRIP_OFFSETS = ExifInterface
-            .getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS);
-    private static final short TAG_STRIP_BYTE_COUNTS = ExifInterface
-            .getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS);
-
-    private final TreeMap<Integer, Object> mCorrespondingEvent = new TreeMap<Integer, Object>();
-
-    private boolean isIfdRequested(int ifdType) {
-        switch (ifdType) {
-            case IfdId.TYPE_IFD_0:
-                return (mOptions & OPTION_IFD_0) != 0;
-            case IfdId.TYPE_IFD_1:
-                return (mOptions & OPTION_IFD_1) != 0;
-            case IfdId.TYPE_IFD_EXIF:
-                return (mOptions & OPTION_IFD_EXIF) != 0;
-            case IfdId.TYPE_IFD_GPS:
-                return (mOptions & OPTION_IFD_GPS) != 0;
-            case IfdId.TYPE_IFD_INTEROPERABILITY:
-                return (mOptions & OPTION_IFD_INTEROPERABILITY) != 0;
-        }
-        return false;
-    }
-
-    private boolean isThumbnailRequested() {
-        return (mOptions & OPTION_THUMBNAIL) != 0;
-    }
-
-    private ExifParser(InputStream inputStream, int options, ExifInterface iRef)
-            throws IOException, ExifInvalidFormatException {
-        if (inputStream == null) {
-            throw new IOException("Null argument inputStream to ExifParser");
-        }
-        if (LOGV) {
-            Log.v(TAG, "Reading exif...");
-        }
-        mInterface = iRef;
-        mContainExifData = seekTiffData(inputStream);
-        mTiffStream = new CountedDataInputStream(inputStream);
-        mOptions = options;
-        if (!mContainExifData) {
-            return;
-        }
-
-        parseTiffHeader();
-        long offset = mTiffStream.readUnsignedInt();
-        if (offset > Integer.MAX_VALUE) {
-            throw new ExifInvalidFormatException("Invalid offset " + offset);
-        }
-        mIfd0Position = (int) offset;
-        mIfdType = IfdId.TYPE_IFD_0;
-        if (isIfdRequested(IfdId.TYPE_IFD_0) || needToParseOffsetsInCurrentIfd()) {
-            registerIfd(IfdId.TYPE_IFD_0, offset);
-            if (offset != DEFAULT_IFD0_OFFSET) {
-                mDataAboveIfd0 = new byte[(int) offset - DEFAULT_IFD0_OFFSET];
-                read(mDataAboveIfd0);
-            }
-        }
-    }
-
-    /**
-     * Parses the the given InputStream with the given options
-     *
-     * @exception IOException
-     * @exception ExifInvalidFormatException
-     */
-    protected static ExifParser parse(InputStream inputStream, int options, ExifInterface iRef)
-            throws IOException, ExifInvalidFormatException {
-        return new ExifParser(inputStream, options, iRef);
-    }
-
-    /**
-     * Parses the the given InputStream with default options; that is, every IFD
-     * and thumbnaill will be parsed.
-     *
-     * @exception IOException
-     * @exception ExifInvalidFormatException
-     * @see #parse(InputStream, int)
-     */
-    protected static ExifParser parse(InputStream inputStream, ExifInterface iRef)
-            throws IOException, ExifInvalidFormatException {
-        return new ExifParser(inputStream, OPTION_IFD_0 | OPTION_IFD_1
-                | OPTION_IFD_EXIF | OPTION_IFD_GPS | OPTION_IFD_INTEROPERABILITY
-                | OPTION_THUMBNAIL, iRef);
-    }
-
-    /**
-     * Moves the parser forward and returns the next parsing event
-     *
-     * @exception IOException
-     * @exception ExifInvalidFormatException
-     * @see #EVENT_START_OF_IFD
-     * @see #EVENT_NEW_TAG
-     * @see #EVENT_VALUE_OF_REGISTERED_TAG
-     * @see #EVENT_COMPRESSED_IMAGE
-     * @see #EVENT_UNCOMPRESSED_STRIP
-     * @see #EVENT_END
-     */
-    protected int next() throws IOException, ExifInvalidFormatException {
-        if (!mContainExifData) {
-            return EVENT_END;
-        }
-        int offset = mTiffStream.getReadByteCount();
-        int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd;
-        if (offset < endOfTags) {
-            mTag = readTag();
-            if (mTag == null) {
-                return next();
-            }
-            if (mNeedToParseOffsetsInCurrentIfd) {
-                checkOffsetOrImageTag(mTag);
-            }
-            return EVENT_NEW_TAG;
-        } else if (offset == endOfTags) {
-            // There is a link to ifd1 at the end of ifd0
-            if (mIfdType == IfdId.TYPE_IFD_0) {
-                long ifdOffset = readUnsignedLong();
-                if (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested()) {
-                    if (ifdOffset != 0) {
-                        registerIfd(IfdId.TYPE_IFD_1, ifdOffset);
-                    }
-                }
-            } else {
-                int offsetSize = 4;
-                // Some camera models use invalid length of the offset
-                if (mCorrespondingEvent.size() > 0) {
-                    offsetSize = mCorrespondingEvent.firstEntry().getKey() -
-                            mTiffStream.getReadByteCount();
-                }
-                if (offsetSize < 4) {
-                    Log.w(TAG, "Invalid size of link to next IFD: " + offsetSize);
-                } else {
-                    long ifdOffset = readUnsignedLong();
-                    if (ifdOffset != 0) {
-                        Log.w(TAG, "Invalid link to next IFD: " + ifdOffset);
-                    }
-                }
-            }
-        }
-        while (mCorrespondingEvent.size() != 0) {
-            Entry<Integer, Object> entry = mCorrespondingEvent.pollFirstEntry();
-            Object event = entry.getValue();
-            try {
-                skipTo(entry.getKey());
-            } catch (IOException e) {
-                Log.w(TAG, "Failed to skip to data at: " + entry.getKey() +
-                        " for " + event.getClass().getName() + ", the file may be broken.");
-                continue;
-            }
-            if (event instanceof IfdEvent) {
-                mIfdType = ((IfdEvent) event).ifd;
-                mNumOfTagInIfd = mTiffStream.readUnsignedShort();
-                mIfdStartOffset = entry.getKey();
-
-                if (mNumOfTagInIfd * TAG_SIZE + mIfdStartOffset + OFFSET_SIZE > mApp1End) {
-                    Log.w(TAG, "Invalid size of IFD " + mIfdType);
-                    return EVENT_END;
-                }
-
-                mNeedToParseOffsetsInCurrentIfd = needToParseOffsetsInCurrentIfd();
-                if (((IfdEvent) event).isRequested) {
-                    return EVENT_START_OF_IFD;
-                } else {
-                    skipRemainingTagsInCurrentIfd();
-                }
-            } else if (event instanceof ImageEvent) {
-                mImageEvent = (ImageEvent) event;
-                return mImageEvent.type;
-            } else {
-                ExifTagEvent tagEvent = (ExifTagEvent) event;
-                mTag = tagEvent.tag;
-                if (mTag.getDataType() != ExifTag.TYPE_UNDEFINED) {
-                    readFullTagValue(mTag);
-                    checkOffsetOrImageTag(mTag);
-                }
-                if (tagEvent.isRequested) {
-                    return EVENT_VALUE_OF_REGISTERED_TAG;
-                }
-            }
-        }
-        return EVENT_END;
-    }
-
-    /**
-     * Skips the tags area of current IFD, if the parser is not in the tag area,
-     * nothing will happen.
-     *
-     * @throws IOException
-     * @throws ExifInvalidFormatException
-     */
-    protected void skipRemainingTagsInCurrentIfd() throws IOException, ExifInvalidFormatException {
-        int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd;
-        int offset = mTiffStream.getReadByteCount();
-        if (offset > endOfTags) {
-            return;
-        }
-        if (mNeedToParseOffsetsInCurrentIfd) {
-            while (offset < endOfTags) {
-                mTag = readTag();
-                offset += TAG_SIZE;
-                if (mTag == null) {
-                    continue;
-                }
-                checkOffsetOrImageTag(mTag);
-            }
-        } else {
-            skipTo(endOfTags);
-        }
-        long ifdOffset = readUnsignedLong();
-        // For ifd0, there is a link to ifd1 in the end of all tags
-        if (mIfdType == IfdId.TYPE_IFD_0
-                && (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested())) {
-            if (ifdOffset > 0) {
-                registerIfd(IfdId.TYPE_IFD_1, ifdOffset);
-            }
-        }
-    }
-
-    private boolean needToParseOffsetsInCurrentIfd() {
-        switch (mIfdType) {
-            case IfdId.TYPE_IFD_0:
-                return isIfdRequested(IfdId.TYPE_IFD_EXIF) || isIfdRequested(IfdId.TYPE_IFD_GPS)
-                        || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)
-                        || isIfdRequested(IfdId.TYPE_IFD_1);
-            case IfdId.TYPE_IFD_1:
-                return isThumbnailRequested();
-            case IfdId.TYPE_IFD_EXIF:
-                // The offset to interoperability IFD is located in Exif IFD
-                return isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY);
-            default:
-                return false;
-        }
-    }
-
-    /**
-     * If {@link #next()} return {@link #EVENT_NEW_TAG} or
-     * {@link #EVENT_VALUE_OF_REGISTERED_TAG}, call this function to get the
-     * corresponding tag.
-     * <p>
-     * For {@link #EVENT_NEW_TAG}, the tag may not contain the value if the size
-     * of the value is greater than 4 bytes. One should call
-     * {@link ExifTag#hasValue()} to check if the tag contains value. If there
-     * is no value,call {@link #registerForTagValue(ExifTag)} to have the parser
-     * emit {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area
-     * pointed by the offset.
-     * <p>
-     * When {@link #EVENT_VALUE_OF_REGISTERED_TAG} is emitted, the value of the
-     * tag will have already been read except for tags of undefined type. For
-     * tags of undefined type, call one of the read methods to get the value.
-     *
-     * @see #registerForTagValue(ExifTag)
-     * @see #read(byte[])
-     * @see #read(byte[], int, int)
-     * @see #readLong()
-     * @see #readRational()
-     * @see #readString(int)
-     * @see #readString(int, Charset)
-     */
-    protected ExifTag getTag() {
-        return mTag;
-    }
-
-    /**
-     * Gets number of tags in the current IFD area.
-     */
-    protected int getTagCountInCurrentIfd() {
-        return mNumOfTagInIfd;
-    }
-
-    /**
-     * Gets the ID of current IFD.
-     *
-     * @see IfdId#TYPE_IFD_0
-     * @see IfdId#TYPE_IFD_1
-     * @see IfdId#TYPE_IFD_GPS
-     * @see IfdId#TYPE_IFD_INTEROPERABILITY
-     * @see IfdId#TYPE_IFD_EXIF
-     */
-    protected int getCurrentIfd() {
-        return mIfdType;
-    }
-
-    /**
-     * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to
-     * get the index of this strip.
-     *
-     * @see #getStripCount()
-     */
-    protected int getStripIndex() {
-        return mImageEvent.stripIndex;
-    }
-
-    /**
-     * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to
-     * get the number of strip data.
-     *
-     * @see #getStripIndex()
-     */
-    protected int getStripCount() {
-        return mStripCount;
-    }
-
-    /**
-     * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to
-     * get the strip size.
-     */
-    protected int getStripSize() {
-        if (mStripSizeTag == null)
-            return 0;
-        return (int) mStripSizeTag.getValueAt(0);
-    }
-
-    /**
-     * When receiving {@link #EVENT_COMPRESSED_IMAGE}, call this function to get
-     * the image data size.
-     */
-    protected int getCompressedImageSize() {
-        if (mJpegSizeTag == null) {
-            return 0;
-        }
-        return (int) mJpegSizeTag.getValueAt(0);
-    }
-
-    private void skipTo(int offset) throws IOException {
-        mTiffStream.skipTo(offset);
-        while (!mCorrespondingEvent.isEmpty() && mCorrespondingEvent.firstKey() < offset) {
-            mCorrespondingEvent.pollFirstEntry();
-        }
-    }
-
-    /**
-     * When getting {@link #EVENT_NEW_TAG} in the tag area of IFD, the tag may
-     * not contain the value if the size of the value is greater than 4 bytes.
-     * When the value is not available here, call this method so that the parser
-     * will emit {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area
-     * where the value is located.
-     *
-     * @see #EVENT_VALUE_OF_REGISTERED_TAG
-     */
-    protected void registerForTagValue(ExifTag tag) {
-        if (tag.getOffset() >= mTiffStream.getReadByteCount()) {
-            mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, true));
-        }
-    }
-
-    private void registerIfd(int ifdType, long offset) {
-        // Cast unsigned int to int since the offset is always smaller
-        // than the size of APP1 (65536)
-        mCorrespondingEvent.put((int) offset, new IfdEvent(ifdType, isIfdRequested(ifdType)));
-    }
-
-    private void registerCompressedImage(long offset) {
-        mCorrespondingEvent.put((int) offset, new ImageEvent(EVENT_COMPRESSED_IMAGE));
-    }
-
-    private void registerUncompressedStrip(int stripIndex, long offset) {
-        mCorrespondingEvent.put((int) offset, new ImageEvent(EVENT_UNCOMPRESSED_STRIP
-                , stripIndex));
-    }
-
-    private ExifTag readTag() throws IOException, ExifInvalidFormatException {
-        short tagId = mTiffStream.readShort();
-        short dataFormat = mTiffStream.readShort();
-        long numOfComp = mTiffStream.readUnsignedInt();
-        if (numOfComp > Integer.MAX_VALUE) {
-            throw new ExifInvalidFormatException(
-                    "Number of component is larger then Integer.MAX_VALUE");
-        }
-        // Some invalid image file contains invalid data type. Ignore those tags
-        if (!ExifTag.isValidType(dataFormat)) {
-            Log.w(TAG, String.format("Tag %04x: Invalid data type %d", tagId, dataFormat));
-            mTiffStream.skip(4);
-            return null;
-        }
-        // TODO: handle numOfComp overflow
-        ExifTag tag = new ExifTag(tagId, dataFormat, (int) numOfComp, mIfdType,
-                ((int) numOfComp) != ExifTag.SIZE_UNDEFINED);
-        int dataSize = tag.getDataSize();
-        if (dataSize > 4) {
-            long offset = mTiffStream.readUnsignedInt();
-            if (offset > Integer.MAX_VALUE) {
-                throw new ExifInvalidFormatException(
-                        "offset is larger then Integer.MAX_VALUE");
-            }
-            // Some invalid images put some undefined data before IFD0.
-            // Read the data here.
-            if ((offset < mIfd0Position) && (dataFormat == ExifTag.TYPE_UNDEFINED)) {
-                byte[] buf = new byte[(int) numOfComp];
-                System.arraycopy(mDataAboveIfd0, (int) offset - DEFAULT_IFD0_OFFSET,
-                        buf, 0, (int) numOfComp);
-                tag.setValue(buf);
-            } else {
-                tag.setOffset((int) offset);
-            }
-        } else {
-            boolean defCount = tag.hasDefinedCount();
-            // Set defined count to 0 so we can add \0 to non-terminated strings
-            tag.setHasDefinedCount(false);
-            // Read value
-            readFullTagValue(tag);
-            tag.setHasDefinedCount(defCount);
-            mTiffStream.skip(4 - dataSize);
-            // Set the offset to the position of value.
-            tag.setOffset(mTiffStream.getReadByteCount() - 4);
-        }
-        return tag;
-    }
-
-    /**
-     * Check the tag, if the tag is one of the offset tag that points to the IFD
-     * or image the caller is interested in, register the IFD or image.
-     */
-    private void checkOffsetOrImageTag(ExifTag tag) {
-        // Some invalid formattd image contains tag with 0 size.
-        if (tag.getComponentCount() == 0) {
-            return;
-        }
-        short tid = tag.getTagId();
-        int ifd = tag.getIfd();
-        if (tid == TAG_EXIF_IFD && checkAllowed(ifd, ExifInterface.TAG_EXIF_IFD)) {
-            if (isIfdRequested(IfdId.TYPE_IFD_EXIF)
-                    || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) {
-                registerIfd(IfdId.TYPE_IFD_EXIF, tag.getValueAt(0));
-            }
-        } else if (tid == TAG_GPS_IFD && checkAllowed(ifd, ExifInterface.TAG_GPS_IFD)) {
-            if (isIfdRequested(IfdId.TYPE_IFD_GPS)) {
-                registerIfd(IfdId.TYPE_IFD_GPS, tag.getValueAt(0));
-            }
-        } else if (tid == TAG_INTEROPERABILITY_IFD
-                && checkAllowed(ifd, ExifInterface.TAG_INTEROPERABILITY_IFD)) {
-            if (isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) {
-                registerIfd(IfdId.TYPE_IFD_INTEROPERABILITY, tag.getValueAt(0));
-            }
-        } else if (tid == TAG_JPEG_INTERCHANGE_FORMAT
-                && checkAllowed(ifd, ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT)) {
-            if (isThumbnailRequested()) {
-                registerCompressedImage(tag.getValueAt(0));
-            }
-        } else if (tid == TAG_JPEG_INTERCHANGE_FORMAT_LENGTH
-                && checkAllowed(ifd, ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH)) {
-            if (isThumbnailRequested()) {
-                mJpegSizeTag = tag;
-            }
-        } else if (tid == TAG_STRIP_OFFSETS && checkAllowed(ifd, ExifInterface.TAG_STRIP_OFFSETS)) {
-            if (isThumbnailRequested()) {
-                if (tag.hasValue()) {
-                    for (int i = 0; i < tag.getComponentCount(); i++) {
-                        if (tag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) {
-                            registerUncompressedStrip(i, tag.getValueAt(i));
-                        } else {
-                            registerUncompressedStrip(i, tag.getValueAt(i));
-                        }
-                    }
-                } else {
-                    mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, false));
-                }
-            }
-        } else if (tid == TAG_STRIP_BYTE_COUNTS
-                && checkAllowed(ifd, ExifInterface.TAG_STRIP_BYTE_COUNTS)
-                &&isThumbnailRequested() && tag.hasValue()) {
-            mStripSizeTag = tag;
-        }
-    }
-
-    private boolean checkAllowed(int ifd, int tagId) {
-        int info = mInterface.getTagInfo().get(tagId);
-        if (info == ExifInterface.DEFINITION_NULL) {
-            return false;
-        }
-        return ExifInterface.isIfdAllowed(info, ifd);
-    }
-
-    protected void readFullTagValue(ExifTag tag) throws IOException {
-        // Some invalid images contains tags with wrong size, check it here
-        short type = tag.getDataType();
-        if (type == ExifTag.TYPE_ASCII || type == ExifTag.TYPE_UNDEFINED ||
-                type == ExifTag.TYPE_UNSIGNED_BYTE) {
-            int size = tag.getComponentCount();
-            if (mCorrespondingEvent.size() > 0) {
-                if (mCorrespondingEvent.firstEntry().getKey() < mTiffStream.getReadByteCount()
-                        + size) {
-                    Object event = mCorrespondingEvent.firstEntry().getValue();
-                    if (event instanceof ImageEvent) {
-                        // Tag value overlaps thumbnail, ignore thumbnail.
-                        Log.w(TAG, "Thumbnail overlaps value for tag: \n" + tag.toString());
-                        Entry<Integer, Object> entry = mCorrespondingEvent.pollFirstEntry();
-                        Log.w(TAG, "Invalid thumbnail offset: " + entry.getKey());
-                    } else {
-                        // Tag value overlaps another tag, shorten count
-                        if (event instanceof IfdEvent) {
-                            Log.w(TAG, "Ifd " + ((IfdEvent) event).ifd
-                                    + " overlaps value for tag: \n" + tag.toString());
-                        } else if (event instanceof ExifTagEvent) {
-                            Log.w(TAG, "Tag value for tag: \n"
-                                    + ((ExifTagEvent) event).tag.toString()
-                                    + " overlaps value for tag: \n" + tag.toString());
-                        }
-                        size = mCorrespondingEvent.firstEntry().getKey()
-                                - mTiffStream.getReadByteCount();
-                        Log.w(TAG, "Invalid size of tag: \n" + tag.toString()
-                                + " setting count to: " + size);
-                        tag.forceSetComponentCount(size);
-                    }
-                }
-            }
-        }
-        switch (tag.getDataType()) {
-            case ExifTag.TYPE_UNSIGNED_BYTE:
-            case ExifTag.TYPE_UNDEFINED: {
-                byte buf[] = new byte[tag.getComponentCount()];
-                read(buf);
-                tag.setValue(buf);
-            }
-                break;
-            case ExifTag.TYPE_ASCII:
-                tag.setValue(readString(tag.getComponentCount()));
-                break;
-            case ExifTag.TYPE_UNSIGNED_LONG: {
-                long value[] = new long[tag.getComponentCount()];
-                for (int i = 0, n = value.length; i < n; i++) {
-                    value[i] = readUnsignedLong();
-                }
-                tag.setValue(value);
-            }
-                break;
-            case ExifTag.TYPE_UNSIGNED_RATIONAL: {
-                Rational value[] = new Rational[tag.getComponentCount()];
-                for (int i = 0, n = value.length; i < n; i++) {
-                    value[i] = readUnsignedRational();
-                }
-                tag.setValue(value);
-            }
-                break;
-            case ExifTag.TYPE_UNSIGNED_SHORT: {
-                int value[] = new int[tag.getComponentCount()];
-                for (int i = 0, n = value.length; i < n; i++) {
-                    value[i] = readUnsignedShort();
-                }
-                tag.setValue(value);
-            }
-                break;
-            case ExifTag.TYPE_LONG: {
-                int value[] = new int[tag.getComponentCount()];
-                for (int i = 0, n = value.length; i < n; i++) {
-                    value[i] = readLong();
-                }
-                tag.setValue(value);
-            }
-                break;
-            case ExifTag.TYPE_RATIONAL: {
-                Rational value[] = new Rational[tag.getComponentCount()];
-                for (int i = 0, n = value.length; i < n; i++) {
-                    value[i] = readRational();
-                }
-                tag.setValue(value);
-            }
-                break;
-        }
-        if (LOGV) {
-            Log.v(TAG, "\n" + tag.toString());
-        }
-    }
-
-    private void parseTiffHeader() throws IOException,
-            ExifInvalidFormatException {
-        short byteOrder = mTiffStream.readShort();
-        if (LITTLE_ENDIAN_TAG == byteOrder) {
-            mTiffStream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
-        } else if (BIG_ENDIAN_TAG == byteOrder) {
-            mTiffStream.setByteOrder(ByteOrder.BIG_ENDIAN);
-        } else {
-            throw new ExifInvalidFormatException("Invalid TIFF header");
-        }
-
-        if (mTiffStream.readShort() != TIFF_HEADER_TAIL) {
-            throw new ExifInvalidFormatException("Invalid TIFF header");
-        }
-    }
-
-    private boolean seekTiffData(InputStream inputStream) throws IOException,
-            ExifInvalidFormatException {
-        CountedDataInputStream dataStream = new CountedDataInputStream(inputStream);
-        if (dataStream.readShort() != JpegHeader.SOI) {
-            throw new ExifInvalidFormatException("Invalid JPEG format");
-        }
-
-        short marker = dataStream.readShort();
-        while (marker != JpegHeader.EOI
-                && !JpegHeader.isSofMarker(marker)) {
-            int length = dataStream.readUnsignedShort();
-            // Some invalid formatted image contains multiple APP1,
-            // try to find the one with Exif data.
-            if (marker == JpegHeader.APP1) {
-                int header = 0;
-                short headerTail = 0;
-                if (length >= 8) {
-                    header = dataStream.readInt();
-                    headerTail = dataStream.readShort();
-                    length -= 6;
-                    if (header == EXIF_HEADER && headerTail == EXIF_HEADER_TAIL) {
-                        mTiffStartPosition = dataStream.getReadByteCount();
-                        mApp1End = length;
-                        mOffsetToApp1EndFromSOF = mTiffStartPosition + mApp1End;
-                        return true;
-                    }
-                }
-            }
-            if (length < 2 || (length - 2) != dataStream.skip(length - 2)) {
-                Log.w(TAG, "Invalid JPEG format.");
-                return false;
-            }
-            marker = dataStream.readShort();
-        }
-        return false;
-    }
-
-    protected int getOffsetToExifEndFromSOF() {
-        return mOffsetToApp1EndFromSOF;
-    }
-
-    protected int getTiffStartPosition() {
-        return mTiffStartPosition;
-    }
-
-    /**
-     * Reads bytes from the InputStream.
-     */
-    protected int read(byte[] buffer, int offset, int length) throws IOException {
-        return mTiffStream.read(buffer, offset, length);
-    }
-
-    /**
-     * Equivalent to read(buffer, 0, buffer.length).
-     */
-    protected int read(byte[] buffer) throws IOException {
-        return mTiffStream.read(buffer);
-    }
-
-    /**
-     * Reads a String from the InputStream with US-ASCII charset. The parser
-     * will read n bytes and convert it to ascii string. This is used for
-     * reading values of type {@link ExifTag#TYPE_ASCII}.
-     */
-    protected String readString(int n) throws IOException {
-        return readString(n, US_ASCII);
-    }
-
-    /**
-     * Reads a String from the InputStream with the given charset. The parser
-     * will read n bytes and convert it to string. This is used for reading
-     * values of type {@link ExifTag#TYPE_ASCII}.
-     */
-    protected String readString(int n, Charset charset) throws IOException {
-        if (n > 0) {
-            return mTiffStream.readString(n, charset);
-        } else {
-            return "";
-        }
-    }
-
-    /**
-     * Reads value of type {@link ExifTag#TYPE_UNSIGNED_SHORT} from the
-     * InputStream.
-     */
-    protected int readUnsignedShort() throws IOException {
-        return mTiffStream.readShort() & 0xffff;
-    }
-
-    /**
-     * Reads value of type {@link ExifTag#TYPE_UNSIGNED_LONG} from the
-     * InputStream.
-     */
-    protected long readUnsignedLong() throws IOException {
-        return readLong() & 0xffffffffL;
-    }
-
-    /**
-     * Reads value of type {@link ExifTag#TYPE_UNSIGNED_RATIONAL} from the
-     * InputStream.
-     */
-    protected Rational readUnsignedRational() throws IOException {
-        long nomi = readUnsignedLong();
-        long denomi = readUnsignedLong();
-        return new Rational(nomi, denomi);
-    }
-
-    /**
-     * Reads value of type {@link ExifTag#TYPE_LONG} from the InputStream.
-     */
-    protected int readLong() throws IOException {
-        return mTiffStream.readInt();
-    }
-
-    /**
-     * Reads value of type {@link ExifTag#TYPE_RATIONAL} from the InputStream.
-     */
-    protected Rational readRational() throws IOException {
-        int nomi = readLong();
-        int denomi = readLong();
-        return new Rational(nomi, denomi);
-    }
-
-    private static class ImageEvent {
-        int stripIndex;
-        int type;
-
-        ImageEvent(int type) {
-            this.stripIndex = 0;
-            this.type = type;
-        }
-
-        ImageEvent(int type, int stripIndex) {
-            this.type = type;
-            this.stripIndex = stripIndex;
-        }
-    }
-
-    private static class IfdEvent {
-        int ifd;
-        boolean isRequested;
-
-        IfdEvent(int ifd, boolean isInterestedIfd) {
-            this.ifd = ifd;
-            this.isRequested = isInterestedIfd;
-        }
-    }
-
-    private static class ExifTagEvent {
-        ExifTag tag;
-        boolean isRequested;
-
-        ExifTagEvent(ExifTag tag, boolean isRequireByUser) {
-            this.tag = tag;
-            this.isRequested = isRequireByUser;
-        }
-    }
-
-    /**
-     * Gets the byte order of the current InputStream.
-     */
-    protected ByteOrder getByteOrder() {
-        return mTiffStream.getByteOrder();
-    }
-}
diff --git a/WallpaperPicker/src/com/android/gallery3d/exif/ExifReader.java b/WallpaperPicker/src/com/android/gallery3d/exif/ExifReader.java
deleted file mode 100644
index 68e972f..0000000
--- a/WallpaperPicker/src/com/android/gallery3d/exif/ExifReader.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2012 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.exif;
-
-import android.util.Log;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * This class reads the EXIF header of a JPEG file and stores it in
- * {@link ExifData}.
- */
-class ExifReader {
-    private static final String TAG = "ExifReader";
-
-    private final ExifInterface mInterface;
-
-    ExifReader(ExifInterface iRef) {
-        mInterface = iRef;
-    }
-
-    /**
-     * Parses the inputStream and and returns the EXIF data in an
-     * {@link ExifData}.
-     *
-     * @throws ExifInvalidFormatException
-     * @throws IOException
-     */
-    protected ExifData read(InputStream inputStream) throws ExifInvalidFormatException,
-            IOException {
-        ExifParser parser = ExifParser.parse(inputStream, mInterface);
-        ExifData exifData = new ExifData(parser.getByteOrder());
-        ExifTag tag = null;
-
-        int event = parser.next();
-        while (event != ExifParser.EVENT_END) {
-            switch (event) {
-                case ExifParser.EVENT_START_OF_IFD:
-                    exifData.addIfdData(new IfdData(parser.getCurrentIfd()));
-                    break;
-                case ExifParser.EVENT_NEW_TAG:
-                    tag = parser.getTag();
-                    if (!tag.hasValue()) {
-                        parser.registerForTagValue(tag);
-                    } else {
-                        exifData.getIfdData(tag.getIfd()).setTag(tag);
-                    }
-                    break;
-                case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
-                    tag = parser.getTag();
-                    if (tag.getDataType() == ExifTag.TYPE_UNDEFINED) {
-                        parser.readFullTagValue(tag);
-                    }
-                    exifData.getIfdData(tag.getIfd()).setTag(tag);
-                    break;
-                case ExifParser.EVENT_COMPRESSED_IMAGE:
-                    byte buf[] = new byte[parser.getCompressedImageSize()];
-                    if (buf.length == parser.read(buf)) {
-                        exifData.setCompressedThumbnail(buf);
-                    } else {
-                        Log.w(TAG, "Failed to read the compressed thumbnail");
-                    }
-                    break;
-                case ExifParser.EVENT_UNCOMPRESSED_STRIP:
-                    buf = new byte[parser.getStripSize()];
-                    if (buf.length == parser.read(buf)) {
-                        exifData.setStripBytes(parser.getStripIndex(), buf);
-                    } else {
-                        Log.w(TAG, "Failed to read the strip bytes");
-                    }
-                    break;
-            }
-            event = parser.next();
-        }
-        return exifData;
-    }
-}
diff --git a/WallpaperPicker/src/com/android/gallery3d/exif/ExifTag.java b/WallpaperPicker/src/com/android/gallery3d/exif/ExifTag.java
deleted file mode 100644
index b8b3872..0000000
--- a/WallpaperPicker/src/com/android/gallery3d/exif/ExifTag.java
+++ /dev/null
@@ -1,1008 +0,0 @@
-/*
- * Copyright (C) 2012 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.exif;
-
-import java.nio.charset.Charset;
-import java.text.SimpleDateFormat;
-import java.util.Arrays;
-import java.util.Date;
-
-/**
- * This class stores information of an EXIF tag. For more information about
- * defined EXIF tags, please read the Jeita EXIF 2.2 standard. Tags should be
- * instantiated using {@link ExifInterface#buildTag}.
- *
- * @see ExifInterface
- */
-public class ExifTag {
-    /**
-     * The BYTE type in the EXIF standard. An 8-bit unsigned integer.
-     */
-    public static final short TYPE_UNSIGNED_BYTE = 1;
-    /**
-     * The ASCII type in the EXIF standard. An 8-bit byte containing one 7-bit
-     * ASCII code. The final byte is terminated with NULL.
-     */
-    public static final short TYPE_ASCII = 2;
-    /**
-     * The SHORT type in the EXIF standard. A 16-bit (2-byte) unsigned integer
-     */
-    public static final short TYPE_UNSIGNED_SHORT = 3;
-    /**
-     * The LONG type in the EXIF standard. A 32-bit (4-byte) unsigned integer
-     */
-    public static final short TYPE_UNSIGNED_LONG = 4;
-    /**
-     * The RATIONAL type of EXIF standard. It consists of two LONGs. The first
-     * one is the numerator and the second one expresses the denominator.
-     */
-    public static final short TYPE_UNSIGNED_RATIONAL = 5;
-    /**
-     * The UNDEFINED type in the EXIF standard. An 8-bit byte that can take any
-     * value depending on the field definition.
-     */
-    public static final short TYPE_UNDEFINED = 7;
-    /**
-     * The SLONG type in the EXIF standard. A 32-bit (4-byte) signed integer
-     * (2's complement notation).
-     */
-    public static final short TYPE_LONG = 9;
-    /**
-     * The SRATIONAL type of EXIF standard. It consists of two SLONGs. The first
-     * one is the numerator and the second one is the denominator.
-     */
-    public static final short TYPE_RATIONAL = 10;
-
-    private static Charset US_ASCII = Charset.forName("US-ASCII");
-    private static final int TYPE_TO_SIZE_MAP[] = new int[11];
-    private static final int UNSIGNED_SHORT_MAX = 65535;
-    private static final long UNSIGNED_LONG_MAX = 4294967295L;
-    private static final long LONG_MAX = Integer.MAX_VALUE;
-    private static final long LONG_MIN = Integer.MIN_VALUE;
-
-    static {
-        TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_BYTE] = 1;
-        TYPE_TO_SIZE_MAP[TYPE_ASCII] = 1;
-        TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_SHORT] = 2;
-        TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_LONG] = 4;
-        TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_RATIONAL] = 8;
-        TYPE_TO_SIZE_MAP[TYPE_UNDEFINED] = 1;
-        TYPE_TO_SIZE_MAP[TYPE_LONG] = 4;
-        TYPE_TO_SIZE_MAP[TYPE_RATIONAL] = 8;
-    }
-
-    static final int SIZE_UNDEFINED = 0;
-
-    // Exif TagId
-    private final short mTagId;
-    // Exif Tag Type
-    private final short mDataType;
-    // If tag has defined count
-    private boolean mHasDefinedDefaultComponentCount;
-    // Actual data count in tag (should be number of elements in value array)
-    private int mComponentCountActual;
-    // The ifd that this tag should be put in
-    private int mIfd;
-    // The value (array of elements of type Tag Type)
-    private Object mValue;
-    // Value offset in exif header.
-    private int mOffset;
-
-    private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("yyyy:MM:dd kk:mm:ss");
-
-    /**
-     * Returns true if the given IFD is a valid IFD.
-     */
-    public static boolean isValidIfd(int ifdId) {
-        return ifdId == IfdId.TYPE_IFD_0 || ifdId == IfdId.TYPE_IFD_1
-                || ifdId == IfdId.TYPE_IFD_EXIF || ifdId == IfdId.TYPE_IFD_INTEROPERABILITY
-                || ifdId == IfdId.TYPE_IFD_GPS;
-    }
-
-    /**
-     * Returns true if a given type is a valid tag type.
-     */
-    public static boolean isValidType(short type) {
-        return type == TYPE_UNSIGNED_BYTE || type == TYPE_ASCII ||
-                type == TYPE_UNSIGNED_SHORT || type == TYPE_UNSIGNED_LONG ||
-                type == TYPE_UNSIGNED_RATIONAL || type == TYPE_UNDEFINED ||
-                type == TYPE_LONG || type == TYPE_RATIONAL;
-    }
-
-    // Use builtTag in ExifInterface instead of constructor.
-    ExifTag(short tagId, short type, int componentCount, int ifd,
-            boolean hasDefinedComponentCount) {
-        mTagId = tagId;
-        mDataType = type;
-        mComponentCountActual = componentCount;
-        mHasDefinedDefaultComponentCount = hasDefinedComponentCount;
-        mIfd = ifd;
-        mValue = null;
-    }
-
-    /**
-     * Gets the element size of the given data type in bytes.
-     *
-     * @see #TYPE_ASCII
-     * @see #TYPE_LONG
-     * @see #TYPE_RATIONAL
-     * @see #TYPE_UNDEFINED
-     * @see #TYPE_UNSIGNED_BYTE
-     * @see #TYPE_UNSIGNED_LONG
-     * @see #TYPE_UNSIGNED_RATIONAL
-     * @see #TYPE_UNSIGNED_SHORT
-     */
-    public static int getElementSize(short type) {
-        return TYPE_TO_SIZE_MAP[type];
-    }
-
-    /**
-     * Returns the ID of the IFD this tag belongs to.
-     *
-     * @see IfdId#TYPE_IFD_0
-     * @see IfdId#TYPE_IFD_1
-     * @see IfdId#TYPE_IFD_EXIF
-     * @see IfdId#TYPE_IFD_GPS
-     * @see IfdId#TYPE_IFD_INTEROPERABILITY
-     */
-    public int getIfd() {
-        return mIfd;
-    }
-
-    protected void setIfd(int ifdId) {
-        mIfd = ifdId;
-    }
-
-    /**
-     * Gets the TID of this tag.
-     */
-    public short getTagId() {
-        return mTagId;
-    }
-
-    /**
-     * Gets the data type of this tag
-     *
-     * @see #TYPE_ASCII
-     * @see #TYPE_LONG
-     * @see #TYPE_RATIONAL
-     * @see #TYPE_UNDEFINED
-     * @see #TYPE_UNSIGNED_BYTE
-     * @see #TYPE_UNSIGNED_LONG
-     * @see #TYPE_UNSIGNED_RATIONAL
-     * @see #TYPE_UNSIGNED_SHORT
-     */
-    public short getDataType() {
-        return mDataType;
-    }
-
-    /**
-     * Gets the total data size in bytes of the value of this tag.
-     */
-    public int getDataSize() {
-        return getComponentCount() * getElementSize(getDataType());
-    }
-
-    /**
-     * Gets the component count of this tag.
-     */
-
-    // TODO: fix integer overflows with this
-    public int getComponentCount() {
-        return mComponentCountActual;
-    }
-
-    /**
-     * Sets the component count of this tag. Call this function before
-     * setValue() if the length of value does not match the component count.
-     */
-    protected void forceSetComponentCount(int count) {
-        mComponentCountActual = count;
-    }
-
-    /**
-     * Returns true if this ExifTag contains value; otherwise, this tag will
-     * contain an offset value that is determined when the tag is written.
-     */
-    public boolean hasValue() {
-        return mValue != null;
-    }
-
-    /**
-     * Sets integer values into this tag. This method should be used for tags of
-     * type {@link #TYPE_UNSIGNED_SHORT}. This method will fail if:
-     * <ul>
-     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT},
-     * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.</li>
-     * <li>The value overflows.</li>
-     * <li>The value.length does NOT match the component count in the definition
-     * for this tag.</li>
-     * </ul>
-     */
-    public boolean setValue(int[] value) {
-        if (checkBadComponentCount(value.length)) {
-            return false;
-        }
-        if (mDataType != TYPE_UNSIGNED_SHORT && mDataType != TYPE_LONG &&
-                mDataType != TYPE_UNSIGNED_LONG) {
-            return false;
-        }
-        if (mDataType == TYPE_UNSIGNED_SHORT && checkOverflowForUnsignedShort(value)) {
-            return false;
-        } else if (mDataType == TYPE_UNSIGNED_LONG && checkOverflowForUnsignedLong(value)) {
-            return false;
-        }
-
-        long[] data = new long[value.length];
-        for (int i = 0; i < value.length; i++) {
-            data[i] = value[i];
-        }
-        mValue = data;
-        mComponentCountActual = value.length;
-        return true;
-    }
-
-    /**
-     * Sets integer value into this tag. This method should be used for tags of
-     * type {@link #TYPE_UNSIGNED_SHORT}, or {@link #TYPE_LONG}. This method
-     * will fail if:
-     * <ul>
-     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT},
-     * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.</li>
-     * <li>The value overflows.</li>
-     * <li>The component count in the definition of this tag is not 1.</li>
-     * </ul>
-     */
-    public boolean setValue(int value) {
-        return setValue(new int[] {
-                value
-        });
-    }
-
-    /**
-     * Sets long values into this tag. This method should be used for tags of
-     * type {@link #TYPE_UNSIGNED_LONG}. This method will fail if:
-     * <ul>
-     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.</li>
-     * <li>The value overflows.</li>
-     * <li>The value.length does NOT match the component count in the definition
-     * for this tag.</li>
-     * </ul>
-     */
-    public boolean setValue(long[] value) {
-        if (checkBadComponentCount(value.length) || mDataType != TYPE_UNSIGNED_LONG) {
-            return false;
-        }
-        if (checkOverflowForUnsignedLong(value)) {
-            return false;
-        }
-        mValue = value;
-        mComponentCountActual = value.length;
-        return true;
-    }
-
-    /**
-     * Sets long values into this tag. This method should be used for tags of
-     * type {@link #TYPE_UNSIGNED_LONG}. This method will fail if:
-     * <ul>
-     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.</li>
-     * <li>The value overflows.</li>
-     * <li>The component count in the definition for this tag is not 1.</li>
-     * </ul>
-     */
-    public boolean setValue(long value) {
-        return setValue(new long[] {
-                value
-        });
-    }
-
-    /**
-     * Sets a string value into this tag. This method should be used for tags of
-     * type {@link #TYPE_ASCII}. The string is converted to an ASCII string.
-     * Characters that cannot be converted are replaced with '?'. The length of
-     * the string must be equal to either (component count -1) or (component
-     * count). The final byte will be set to the string null terminator '\0',
-     * overwriting the last character in the string if the value.length is equal
-     * to the component count. This method will fail if:
-     * <ul>
-     * <li>The data type is not {@link #TYPE_ASCII} or {@link #TYPE_UNDEFINED}.</li>
-     * <li>The length of the string is not equal to (component count -1) or
-     * (component count) in the definition for this tag.</li>
-     * </ul>
-     */
-    public boolean setValue(String value) {
-        if (mDataType != TYPE_ASCII && mDataType != TYPE_UNDEFINED) {
-            return false;
-        }
-
-        byte[] buf = value.getBytes(US_ASCII);
-        byte[] finalBuf = buf;
-        if (buf.length > 0) {
-            finalBuf = (buf[buf.length - 1] == 0 || mDataType == TYPE_UNDEFINED) ? buf : Arrays
-                .copyOf(buf, buf.length + 1);
-        } else if (mDataType == TYPE_ASCII && mComponentCountActual == 1) {
-            finalBuf = new byte[] { 0 };
-        }
-        int count = finalBuf.length;
-        if (checkBadComponentCount(count)) {
-            return false;
-        }
-        mComponentCountActual = count;
-        mValue = finalBuf;
-        return true;
-    }
-
-    /**
-     * Sets Rational values into this tag. This method should be used for tags
-     * of type {@link #TYPE_UNSIGNED_RATIONAL}, or {@link #TYPE_RATIONAL}. This
-     * method will fail if:
-     * <ul>
-     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL}
-     * or {@link #TYPE_RATIONAL}.</li>
-     * <li>The value overflows.</li>
-     * <li>The value.length does NOT match the component count in the definition
-     * for this tag.</li>
-     * </ul>
-     *
-     * @see Rational
-     */
-    public boolean setValue(Rational[] value) {
-        if (checkBadComponentCount(value.length)) {
-            return false;
-        }
-        if (mDataType != TYPE_UNSIGNED_RATIONAL && mDataType != TYPE_RATIONAL) {
-            return false;
-        }
-        if (mDataType == TYPE_UNSIGNED_RATIONAL && checkOverflowForUnsignedRational(value)) {
-            return false;
-        } else if (mDataType == TYPE_RATIONAL && checkOverflowForRational(value)) {
-            return false;
-        }
-
-        mValue = value;
-        mComponentCountActual = value.length;
-        return true;
-    }
-
-    /**
-     * Sets a Rational value into this tag. This method should be used for tags
-     * of type {@link #TYPE_UNSIGNED_RATIONAL}, or {@link #TYPE_RATIONAL}. This
-     * method will fail if:
-     * <ul>
-     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL}
-     * or {@link #TYPE_RATIONAL}.</li>
-     * <li>The value overflows.</li>
-     * <li>The component count in the definition for this tag is not 1.</li>
-     * </ul>
-     *
-     * @see Rational
-     */
-    public boolean setValue(Rational value) {
-        return setValue(new Rational[] {
-                value
-        });
-    }
-
-    /**
-     * Sets byte values into this tag. This method should be used for tags of
-     * type {@link #TYPE_UNSIGNED_BYTE} or {@link #TYPE_UNDEFINED}. This method
-     * will fail if:
-     * <ul>
-     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or
-     * {@link #TYPE_UNDEFINED} .</li>
-     * <li>The length does NOT match the component count in the definition for
-     * this tag.</li>
-     * </ul>
-     */
-    public boolean setValue(byte[] value, int offset, int length) {
-        if (checkBadComponentCount(length)) {
-            return false;
-        }
-        if (mDataType != TYPE_UNSIGNED_BYTE && mDataType != TYPE_UNDEFINED) {
-            return false;
-        }
-        mValue = new byte[length];
-        System.arraycopy(value, offset, mValue, 0, length);
-        mComponentCountActual = length;
-        return true;
-    }
-
-    /**
-     * Equivalent to setValue(value, 0, value.length).
-     */
-    public boolean setValue(byte[] value) {
-        return setValue(value, 0, value.length);
-    }
-
-    /**
-     * Sets byte value into this tag. This method should be used for tags of
-     * type {@link #TYPE_UNSIGNED_BYTE} or {@link #TYPE_UNDEFINED}. This method
-     * will fail if:
-     * <ul>
-     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or
-     * {@link #TYPE_UNDEFINED} .</li>
-     * <li>The component count in the definition for this tag is not 1.</li>
-     * </ul>
-     */
-    public boolean setValue(byte value) {
-        return setValue(new byte[] {
-                value
-        });
-    }
-
-    /**
-     * Sets the value for this tag using an appropriate setValue method for the
-     * given object. This method will fail if:
-     * <ul>
-     * <li>The corresponding setValue method for the class of the object passed
-     * in would fail.</li>
-     * <li>There is no obvious way to cast the object passed in into an EXIF tag
-     * type.</li>
-     * </ul>
-     */
-    public boolean setValue(Object obj) {
-        if (obj == null) {
-            return false;
-        } else if (obj instanceof Short) {
-            return setValue(((Short) obj).shortValue() & 0x0ffff);
-        } else if (obj instanceof String) {
-            return setValue((String) obj);
-        } else if (obj instanceof int[]) {
-            return setValue((int[]) obj);
-        } else if (obj instanceof long[]) {
-            return setValue((long[]) obj);
-        } else if (obj instanceof Rational) {
-            return setValue((Rational) obj);
-        } else if (obj instanceof Rational[]) {
-            return setValue((Rational[]) obj);
-        } else if (obj instanceof byte[]) {
-            return setValue((byte[]) obj);
-        } else if (obj instanceof Integer) {
-            return setValue(((Integer) obj).intValue());
-        } else if (obj instanceof Long) {
-            return setValue(((Long) obj).longValue());
-        } else if (obj instanceof Byte) {
-            return setValue(((Byte) obj).byteValue());
-        } else if (obj instanceof Short[]) {
-            // Nulls in this array are treated as zeroes.
-            Short[] arr = (Short[]) obj;
-            int[] fin = new int[arr.length];
-            for (int i = 0; i < arr.length; i++) {
-                fin[i] = (arr[i] == null) ? 0 : arr[i].shortValue() & 0x0ffff;
-            }
-            return setValue(fin);
-        } else if (obj instanceof Integer[]) {
-            // Nulls in this array are treated as zeroes.
-            Integer[] arr = (Integer[]) obj;
-            int[] fin = new int[arr.length];
-            for (int i = 0; i < arr.length; i++) {
-                fin[i] = (arr[i] == null) ? 0 : arr[i].intValue();
-            }
-            return setValue(fin);
-        } else if (obj instanceof Long[]) {
-            // Nulls in this array are treated as zeroes.
-            Long[] arr = (Long[]) obj;
-            long[] fin = new long[arr.length];
-            for (int i = 0; i < arr.length; i++) {
-                fin[i] = (arr[i] == null) ? 0 : arr[i].longValue();
-            }
-            return setValue(fin);
-        } else if (obj instanceof Byte[]) {
-            // Nulls in this array are treated as zeroes.
-            Byte[] arr = (Byte[]) obj;
-            byte[] fin = new byte[arr.length];
-            for (int i = 0; i < arr.length; i++) {
-                fin[i] = (arr[i] == null) ? 0 : arr[i].byteValue();
-            }
-            return setValue(fin);
-        } else {
-            return false;
-        }
-    }
-
-    /**
-     * Sets a timestamp to this tag. The method converts the timestamp with the
-     * format of "yyyy:MM:dd kk:mm:ss" and calls {@link #setValue(String)}. This
-     * method will fail if the data type is not {@link #TYPE_ASCII} or the
-     * component count of this tag is not 20 or undefined.
-     *
-     * @param time the number of milliseconds since Jan. 1, 1970 GMT
-     * @return true on success
-     */
-    public boolean setTimeValue(long time) {
-        // synchronized on TIME_FORMAT as SimpleDateFormat is not thread safe
-        synchronized (TIME_FORMAT) {
-            return setValue(TIME_FORMAT.format(new Date(time)));
-        }
-    }
-
-    /**
-     * Gets the value as a String. This method should be used for tags of type
-     * {@link #TYPE_ASCII}.
-     *
-     * @return the value as a String, or null if the tag's value does not exist
-     *         or cannot be converted to a String.
-     */
-    public String getValueAsString() {
-        if (mValue == null) {
-            return null;
-        } else if (mValue instanceof String) {
-            return (String) mValue;
-        } else if (mValue instanceof byte[]) {
-            return new String((byte[]) mValue, US_ASCII);
-        }
-        return null;
-    }
-
-    /**
-     * Gets the value as a String. This method should be used for tags of type
-     * {@link #TYPE_ASCII}.
-     *
-     * @param defaultValue the String to return if the tag's value does not
-     *            exist or cannot be converted to a String.
-     * @return the tag's value as a String, or the defaultValue.
-     */
-    public String getValueAsString(String defaultValue) {
-        String s = getValueAsString();
-        if (s == null) {
-            return defaultValue;
-        }
-        return s;
-    }
-
-    /**
-     * Gets the value as a byte array. This method should be used for tags of
-     * type {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}.
-     *
-     * @return the value as a byte array, or null if the tag's value does not
-     *         exist or cannot be converted to a byte array.
-     */
-    public byte[] getValueAsBytes() {
-        if (mValue instanceof byte[]) {
-            return (byte[]) mValue;
-        }
-        return null;
-    }
-
-    /**
-     * Gets the value as a byte. If there are more than 1 bytes in this value,
-     * gets the first byte. This method should be used for tags of type
-     * {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}.
-     *
-     * @param defaultValue the byte to return if tag's value does not exist or
-     *            cannot be converted to a byte.
-     * @return the tag's value as a byte, or the defaultValue.
-     */
-    public byte getValueAsByte(byte defaultValue) {
-        byte[] b = getValueAsBytes();
-        if (b == null || b.length < 1) {
-            return defaultValue;
-        }
-        return b[0];
-    }
-
-    /**
-     * Gets the value as an array of Rationals. This method should be used for
-     * tags of type {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
-     *
-     * @return the value as as an array of Rationals, or null if the tag's value
-     *         does not exist or cannot be converted to an array of Rationals.
-     */
-    public Rational[] getValueAsRationals() {
-        if (mValue instanceof Rational[]) {
-            return (Rational[]) mValue;
-        }
-        return null;
-    }
-
-    /**
-     * Gets the value as a Rational. If there are more than 1 Rationals in this
-     * value, gets the first one. This method should be used for tags of type
-     * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
-     *
-     * @param defaultValue the Rational to return if tag's value does not exist
-     *            or cannot be converted to a Rational.
-     * @return the tag's value as a Rational, or the defaultValue.
-     */
-    public Rational getValueAsRational(Rational defaultValue) {
-        Rational[] r = getValueAsRationals();
-        if (r == null || r.length < 1) {
-            return defaultValue;
-        }
-        return r[0];
-    }
-
-    /**
-     * Gets the value as a Rational. If there are more than 1 Rationals in this
-     * value, gets the first one. This method should be used for tags of type
-     * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
-     *
-     * @param defaultValue the numerator of the Rational to return if tag's
-     *            value does not exist or cannot be converted to a Rational (the
-     *            denominator will be 1).
-     * @return the tag's value as a Rational, or the defaultValue.
-     */
-    public Rational getValueAsRational(long defaultValue) {
-        Rational defaultVal = new Rational(defaultValue, 1);
-        return getValueAsRational(defaultVal);
-    }
-
-    /**
-     * Gets the value as an array of ints. This method should be used for tags
-     * of type {@link #TYPE_UNSIGNED_SHORT}, {@link #TYPE_UNSIGNED_LONG}.
-     *
-     * @return the value as as an array of ints, or null if the tag's value does
-     *         not exist or cannot be converted to an array of ints.
-     */
-    public int[] getValueAsInts() {
-        if (mValue == null) {
-            return null;
-        } else if (mValue instanceof long[]) {
-            long[] val = (long[]) mValue;
-            int[] arr = new int[val.length];
-            for (int i = 0; i < val.length; i++) {
-                arr[i] = (int) val[i]; // Truncates
-            }
-            return arr;
-        }
-        return null;
-    }
-
-    /**
-     * Gets the value as an int. If there are more than 1 ints in this value,
-     * gets the first one. This method should be used for tags of type
-     * {@link #TYPE_UNSIGNED_SHORT}, {@link #TYPE_UNSIGNED_LONG}.
-     *
-     * @param defaultValue the int to return if tag's value does not exist or
-     *            cannot be converted to an int.
-     * @return the tag's value as a int, or the defaultValue.
-     */
-    public int getValueAsInt(int defaultValue) {
-        int[] i = getValueAsInts();
-        if (i == null || i.length < 1) {
-            return defaultValue;
-        }
-        return i[0];
-    }
-
-    /**
-     * Gets the value as an array of longs. This method should be used for tags
-     * of type {@link #TYPE_UNSIGNED_LONG}.
-     *
-     * @return the value as as an array of longs, or null if the tag's value
-     *         does not exist or cannot be converted to an array of longs.
-     */
-    public long[] getValueAsLongs() {
-        if (mValue instanceof long[]) {
-            return (long[]) mValue;
-        }
-        return null;
-    }
-
-    /**
-     * Gets the value or null if none exists. If there are more than 1 longs in
-     * this value, gets the first one. This method should be used for tags of
-     * type {@link #TYPE_UNSIGNED_LONG}.
-     *
-     * @param defaultValue the long to return if tag's value does not exist or
-     *            cannot be converted to a long.
-     * @return the tag's value as a long, or the defaultValue.
-     */
-    public long getValueAsLong(long defaultValue) {
-        long[] l = getValueAsLongs();
-        if (l == null || l.length < 1) {
-            return defaultValue;
-        }
-        return l[0];
-    }
-
-    /**
-     * Gets the tag's value or null if none exists.
-     */
-    public Object getValue() {
-        return mValue;
-    }
-
-    /**
-     * Gets a long representation of the value.
-     *
-     * @param defaultValue value to return if there is no value or value is a
-     *            rational with a denominator of 0.
-     * @return the tag's value as a long, or defaultValue if no representation
-     *         exists.
-     */
-    public long forceGetValueAsLong(long defaultValue) {
-        long[] l = getValueAsLongs();
-        if (l != null && l.length >= 1) {
-            return l[0];
-        }
-        byte[] b = getValueAsBytes();
-        if (b != null && b.length >= 1) {
-            return b[0];
-        }
-        Rational[] r = getValueAsRationals();
-        if (r != null && r.length >= 1 && r[0].getDenominator() != 0) {
-            return (long) r[0].toDouble();
-        }
-        return defaultValue;
-    }
-
-    /**
-     * Gets a string representation of the value.
-     */
-    public String forceGetValueAsString() {
-        if (mValue == null) {
-            return "";
-        } else if (mValue instanceof byte[]) {
-            if (mDataType == TYPE_ASCII) {
-                return new String((byte[]) mValue, US_ASCII);
-            } else {
-                return Arrays.toString((byte[]) mValue);
-            }
-        } else if (mValue instanceof long[]) {
-            if (((long[]) mValue).length == 1) {
-                return String.valueOf(((long[]) mValue)[0]);
-            } else {
-                return Arrays.toString((long[]) mValue);
-            }
-        } else if (mValue instanceof Object[]) {
-            if (((Object[]) mValue).length == 1) {
-                Object val = ((Object[]) mValue)[0];
-                if (val == null) {
-                    return "";
-                } else {
-                    return val.toString();
-                }
-            } else {
-                return Arrays.toString((Object[]) mValue);
-            }
-        } else {
-            return mValue.toString();
-        }
-    }
-
-    /**
-     * Gets the value for type {@link #TYPE_ASCII}, {@link #TYPE_LONG},
-     * {@link #TYPE_UNDEFINED}, {@link #TYPE_UNSIGNED_BYTE},
-     * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_UNSIGNED_SHORT}. For
-     * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}, call
-     * {@link #getRational(int)} instead.
-     *
-     * @exception IllegalArgumentException if the data type is
-     *                {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
-     */
-    protected long getValueAt(int index) {
-        if (mValue instanceof long[]) {
-            return ((long[]) mValue)[index];
-        } else if (mValue instanceof byte[]) {
-            return ((byte[]) mValue)[index];
-        }
-        throw new IllegalArgumentException("Cannot get integer value from "
-                + convertTypeToString(mDataType));
-    }
-
-    /**
-     * Gets the {@link #TYPE_ASCII} data.
-     *
-     * @exception IllegalArgumentException If the type is NOT
-     *                {@link #TYPE_ASCII}.
-     */
-    protected String getString() {
-        if (mDataType != TYPE_ASCII) {
-            throw new IllegalArgumentException("Cannot get ASCII value from "
-                    + convertTypeToString(mDataType));
-        }
-        return new String((byte[]) mValue, US_ASCII);
-    }
-
-    /*
-     * Get the converted ascii byte. Used by ExifOutputStream.
-     */
-    protected byte[] getStringByte() {
-        return (byte[]) mValue;
-    }
-
-    /**
-     * Gets the {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL} data.
-     *
-     * @exception IllegalArgumentException If the type is NOT
-     *                {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
-     */
-    protected Rational getRational(int index) {
-        if ((mDataType != TYPE_RATIONAL) && (mDataType != TYPE_UNSIGNED_RATIONAL)) {
-            throw new IllegalArgumentException("Cannot get RATIONAL value from "
-                    + convertTypeToString(mDataType));
-        }
-        return ((Rational[]) mValue)[index];
-    }
-
-    /**
-     * Equivalent to getBytes(buffer, 0, buffer.length).
-     */
-    protected void getBytes(byte[] buf) {
-        getBytes(buf, 0, buf.length);
-    }
-
-    /**
-     * Gets the {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE} data.
-     *
-     * @param buf the byte array in which to store the bytes read.
-     * @param offset the initial position in buffer to store the bytes.
-     * @param length the maximum number of bytes to store in buffer. If length >
-     *            component count, only the valid bytes will be stored.
-     * @exception IllegalArgumentException If the type is NOT
-     *                {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}.
-     */
-    protected void getBytes(byte[] buf, int offset, int length) {
-        if ((mDataType != TYPE_UNDEFINED) && (mDataType != TYPE_UNSIGNED_BYTE)) {
-            throw new IllegalArgumentException("Cannot get BYTE value from "
-                    + convertTypeToString(mDataType));
-        }
-        System.arraycopy(mValue, 0, buf, offset,
-                (length > mComponentCountActual) ? mComponentCountActual : length);
-    }
-
-    /**
-     * Gets the offset of this tag. This is only valid if this data size > 4 and
-     * contains an offset to the location of the actual value.
-     */
-    protected int getOffset() {
-        return mOffset;
-    }
-
-    /**
-     * Sets the offset of this tag.
-     */
-    protected void setOffset(int offset) {
-        mOffset = offset;
-    }
-
-    protected void setHasDefinedCount(boolean d) {
-        mHasDefinedDefaultComponentCount = d;
-    }
-
-    protected boolean hasDefinedCount() {
-        return mHasDefinedDefaultComponentCount;
-    }
-
-    private boolean checkBadComponentCount(int count) {
-        if (mHasDefinedDefaultComponentCount && (mComponentCountActual != count)) {
-            return true;
-        }
-        return false;
-    }
-
-    private static String convertTypeToString(short type) {
-        switch (type) {
-            case TYPE_UNSIGNED_BYTE:
-                return "UNSIGNED_BYTE";
-            case TYPE_ASCII:
-                return "ASCII";
-            case TYPE_UNSIGNED_SHORT:
-                return "UNSIGNED_SHORT";
-            case TYPE_UNSIGNED_LONG:
-                return "UNSIGNED_LONG";
-            case TYPE_UNSIGNED_RATIONAL:
-                return "UNSIGNED_RATIONAL";
-            case TYPE_UNDEFINED:
-                return "UNDEFINED";
-            case TYPE_LONG:
-                return "LONG";
-            case TYPE_RATIONAL:
-                return "RATIONAL";
-            default:
-                return "";
-        }
-    }
-
-    private boolean checkOverflowForUnsignedShort(int[] value) {
-        for (int v : value) {
-            if (v > UNSIGNED_SHORT_MAX || v < 0) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private boolean checkOverflowForUnsignedLong(long[] value) {
-        for (long v : value) {
-            if (v < 0 || v > UNSIGNED_LONG_MAX) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private boolean checkOverflowForUnsignedLong(int[] value) {
-        for (int v : value) {
-            if (v < 0) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private boolean checkOverflowForUnsignedRational(Rational[] value) {
-        for (Rational v : value) {
-            if (v.getNumerator() < 0 || v.getDenominator() < 0
-                    || v.getNumerator() > UNSIGNED_LONG_MAX
-                    || v.getDenominator() > UNSIGNED_LONG_MAX) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private boolean checkOverflowForRational(Rational[] value) {
-        for (Rational v : value) {
-            if (v.getNumerator() < LONG_MIN || v.getDenominator() < LONG_MIN
-                    || v.getNumerator() > LONG_MAX
-                    || v.getDenominator() > LONG_MAX) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj == null) {
-            return false;
-        }
-        if (obj instanceof ExifTag) {
-            ExifTag tag = (ExifTag) obj;
-            if (tag.mTagId != this.mTagId
-                    || tag.mComponentCountActual != this.mComponentCountActual
-                    || tag.mDataType != this.mDataType) {
-                return false;
-            }
-            if (mValue != null) {
-                if (tag.mValue == null) {
-                    return false;
-                } else if (mValue instanceof long[]) {
-                    if (!(tag.mValue instanceof long[])) {
-                        return false;
-                    }
-                    return Arrays.equals((long[]) mValue, (long[]) tag.mValue);
-                } else if (mValue instanceof Rational[]) {
-                    if (!(tag.mValue instanceof Rational[])) {
-                        return false;
-                    }
-                    return Arrays.equals((Rational[]) mValue, (Rational[]) tag.mValue);
-                } else if (mValue instanceof byte[]) {
-                    if (!(tag.mValue instanceof byte[])) {
-                        return false;
-                    }
-                    return Arrays.equals((byte[]) mValue, (byte[]) tag.mValue);
-                } else {
-                    return mValue.equals(tag.mValue);
-                }
-            } else {
-                return tag.mValue == null;
-            }
-        }
-        return false;
-    }
-
-    @Override
-    public String toString() {
-        return String.format("tag id: %04X\n", mTagId) + "ifd id: " + mIfd + "\ntype: "
-                + convertTypeToString(mDataType) + "\ncount: " + mComponentCountActual
-                + "\noffset: " + mOffset + "\nvalue: " + forceGetValueAsString() + "\n";
-    }
-
-}
diff --git a/WallpaperPicker/src/com/android/gallery3d/exif/IfdData.java b/WallpaperPicker/src/com/android/gallery3d/exif/IfdData.java
deleted file mode 100644
index 093944a..0000000
--- a/WallpaperPicker/src/com/android/gallery3d/exif/IfdData.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2012 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.exif;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * This class stores all the tags in an IFD.
- *
- * @see ExifData
- * @see ExifTag
- */
-class IfdData {
-
-    private final int mIfdId;
-    private final Map<Short, ExifTag> mExifTags = new HashMap<Short, ExifTag>();
-    private int mOffsetToNextIfd = 0;
-    private static final int[] sIfds = {
-            IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_1, IfdId.TYPE_IFD_EXIF,
-            IfdId.TYPE_IFD_INTEROPERABILITY, IfdId.TYPE_IFD_GPS
-    };
-    /**
-     * Creates an IfdData with given IFD ID.
-     *
-     * @see IfdId#TYPE_IFD_0
-     * @see IfdId#TYPE_IFD_1
-     * @see IfdId#TYPE_IFD_EXIF
-     * @see IfdId#TYPE_IFD_GPS
-     * @see IfdId#TYPE_IFD_INTEROPERABILITY
-     */
-    IfdData(int ifdId) {
-        mIfdId = ifdId;
-    }
-
-    static protected int[] getIfds() {
-        return sIfds;
-    }
-
-    /**
-     * Get a array the contains all {@link ExifTag} in this IFD.
-     */
-    protected ExifTag[] getAllTags() {
-        return mExifTags.values().toArray(new ExifTag[mExifTags.size()]);
-    }
-
-    /**
-     * Gets the ID of this IFD.
-     *
-     * @see IfdId#TYPE_IFD_0
-     * @see IfdId#TYPE_IFD_1
-     * @see IfdId#TYPE_IFD_EXIF
-     * @see IfdId#TYPE_IFD_GPS
-     * @see IfdId#TYPE_IFD_INTEROPERABILITY
-     */
-    protected int getId() {
-        return mIfdId;
-    }
-
-    /**
-     * Gets the {@link ExifTag} with given tag id. Return null if there is no
-     * such tag.
-     */
-    protected ExifTag getTag(short tagId) {
-        return mExifTags.get(tagId);
-    }
-
-    /**
-     * Adds or replaces a {@link ExifTag}.
-     */
-    protected ExifTag setTag(ExifTag tag) {
-        tag.setIfd(mIfdId);
-        return mExifTags.put(tag.getTagId(), tag);
-    }
-
-    protected boolean checkCollision(short tagId) {
-        return mExifTags.get(tagId) != null;
-    }
-
-    /**
-     * Removes the tag of the given ID
-     */
-    protected void removeTag(short tagId) {
-        mExifTags.remove(tagId);
-    }
-
-    /**
-     * Gets the tags count in the IFD.
-     */
-    protected int getTagCount() {
-        return mExifTags.size();
-    }
-
-    /**
-     * Sets the offset of next IFD.
-     */
-    protected void setOffsetToNextIfd(int offset) {
-        mOffsetToNextIfd = offset;
-    }
-
-    /**
-     * Gets the offset of next IFD.
-     */
-    protected int getOffsetToNextIfd() {
-        return mOffsetToNextIfd;
-    }
-
-    /**
-     * Returns true if all tags in this two IFDs are equal. Note that tags of
-     * IFDs offset or thumbnail offset will be ignored.
-     */
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj == null) {
-            return false;
-        }
-        if (obj instanceof IfdData) {
-            IfdData data = (IfdData) obj;
-            if (data.getId() == mIfdId && data.getTagCount() == getTagCount()) {
-                ExifTag[] tags = data.getAllTags();
-                for (ExifTag tag : tags) {
-                    if (ExifInterface.isOffsetTag(tag.getTagId())) {
-                        continue;
-                    }
-                    ExifTag tag2 = mExifTags.get(tag.getTagId());
-                    if (!tag.equals(tag2)) {
-                        return false;
-                    }
-                }
-                return true;
-            }
-        }
-        return false;
-    }
-}
diff --git a/WallpaperPicker/src/com/android/gallery3d/exif/IfdId.java b/WallpaperPicker/src/com/android/gallery3d/exif/IfdId.java
deleted file mode 100644
index 7842edb..0000000
--- a/WallpaperPicker/src/com/android/gallery3d/exif/IfdId.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2012 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.exif;
-
-/**
- * The constants of the IFD ID defined in EXIF spec.
- */
-public interface IfdId {
-    public static final int TYPE_IFD_0 = 0;
-    public static final int TYPE_IFD_1 = 1;
-    public static final int TYPE_IFD_EXIF = 2;
-    public static final int TYPE_IFD_INTEROPERABILITY = 3;
-    public static final int TYPE_IFD_GPS = 4;
-    /* This is used in ExifData to allocate enough IfdData */
-    static final int TYPE_IFD_COUNT = 5;
-
-}
diff --git a/WallpaperPicker/src/com/android/gallery3d/exif/JpegHeader.java b/WallpaperPicker/src/com/android/gallery3d/exif/JpegHeader.java
deleted file mode 100644
index e3e787e..0000000
--- a/WallpaperPicker/src/com/android/gallery3d/exif/JpegHeader.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2012 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.exif;
-
-class JpegHeader {
-    public static final short SOI =  (short) 0xFFD8;
-    public static final short APP1 = (short) 0xFFE1;
-    public static final short APP0 = (short) 0xFFE0;
-    public static final short EOI = (short) 0xFFD9;
-
-    /**
-     *  SOF (start of frame). All value between SOF0 and SOF15 is SOF marker except for DHT, JPG,
-     *  and DAC marker.
-     */
-    public static final short SOF0 = (short) 0xFFC0;
-    public static final short SOF15 = (short) 0xFFCF;
-    public static final short DHT = (short) 0xFFC4;
-    public static final short JPG = (short) 0xFFC8;
-    public static final short DAC = (short) 0xFFCC;
-
-    public static final boolean isSofMarker(short marker) {
-        return marker >= SOF0 && marker <= SOF15 && marker != DHT && marker != JPG
-                && marker != DAC;
-    }
-}
diff --git a/WallpaperPicker/src/com/android/gallery3d/exif/OrderedDataOutputStream.java b/WallpaperPicker/src/com/android/gallery3d/exif/OrderedDataOutputStream.java
deleted file mode 100644
index 428e6b9..0000000
--- a/WallpaperPicker/src/com/android/gallery3d/exif/OrderedDataOutputStream.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2012 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.exif;
-
-import java.io.FilterOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-class OrderedDataOutputStream extends FilterOutputStream {
-    private final ByteBuffer mByteBuffer = ByteBuffer.allocate(4);
-
-    public OrderedDataOutputStream(OutputStream out) {
-        super(out);
-    }
-
-    public OrderedDataOutputStream setByteOrder(ByteOrder order) {
-        mByteBuffer.order(order);
-        return this;
-    }
-
-    public OrderedDataOutputStream writeShort(short value) throws IOException {
-        mByteBuffer.rewind();
-        mByteBuffer.putShort(value);
-        out.write(mByteBuffer.array(), 0, 2);
-        return this;
-    }
-
-    public OrderedDataOutputStream writeInt(int value) throws IOException {
-        mByteBuffer.rewind();
-        mByteBuffer.putInt(value);
-        out.write(mByteBuffer.array());
-        return this;
-    }
-
-    public OrderedDataOutputStream writeRational(Rational rational) throws IOException {
-        writeInt((int) rational.getNumerator());
-        writeInt((int) rational.getDenominator());
-        return this;
-    }
-}
diff --git a/WallpaperPicker/src/com/android/gallery3d/exif/Rational.java b/WallpaperPicker/src/com/android/gallery3d/exif/Rational.java
deleted file mode 100644
index 591d63f..0000000
--- a/WallpaperPicker/src/com/android/gallery3d/exif/Rational.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2012 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.exif;
-
-/**
- * The rational data type of EXIF tag. Contains a pair of longs representing the
- * numerator and denominator of a Rational number.
- */
-public class Rational {
-
-    private final long mNumerator;
-    private final long mDenominator;
-
-    /**
-     * Create a Rational with a given numerator and denominator.
-     *
-     * @param nominator
-     * @param denominator
-     */
-    public Rational(long nominator, long denominator) {
-        mNumerator = nominator;
-        mDenominator = denominator;
-    }
-
-    /**
-     * Create a copy of a Rational.
-     */
-    public Rational(Rational r) {
-        mNumerator = r.mNumerator;
-        mDenominator = r.mDenominator;
-    }
-
-    /**
-     * Gets the numerator of the rational.
-     */
-    public long getNumerator() {
-        return mNumerator;
-    }
-
-    /**
-     * Gets the denominator of the rational
-     */
-    public long getDenominator() {
-        return mDenominator;
-    }
-
-    /**
-     * Gets the rational value as type double. Will cause a divide-by-zero error
-     * if the denominator is 0.
-     */
-    public double toDouble() {
-        return mNumerator / (double) mDenominator;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj == null) {
-            return false;
-        }
-        if (this == obj) {
-            return true;
-        }
-        if (obj instanceof Rational) {
-            Rational data = (Rational) obj;
-            return mNumerator == data.mNumerator && mDenominator == data.mDenominator;
-        }
-        return false;
-    }
-
-    @Override
-    public String toString() {
-        return mNumerator + "/" + mDenominator;
-    }
-}
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 f2459dd..b562fbf 100644
--- a/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
+++ b/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
@@ -20,15 +20,14 @@
 import android.app.ActionBar;
 import android.app.Activity;
 import android.app.WallpaperManager;
-import android.content.Context;
 import android.content.Intent;
-import android.content.SharedPreferences;
-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;
@@ -58,24 +57,10 @@
 public class WallpaperCropActivity extends BaseActivity implements Handler.Callback {
     private static final String LOGTAG = "Launcher3.CropActivity";
 
-    protected static final String WALLPAPER_WIDTH_KEY = WallpaperUtils.WALLPAPER_WIDTH_KEY;
-    protected static final String WALLPAPER_HEIGHT_KEY = WallpaperUtils.WALLPAPER_HEIGHT_KEY;
-
-    /**
-     * 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;
-    public static final float WALLPAPER_SCREENS_SPAN = WallpaperUtils.WALLPAPER_SCREENS_SPAN;
-
     private static final int MSG_LOAD_IMAGE = 1;
 
     protected CropView mCropView;
     protected View mProgressView;
-    protected Uri mUri;
     protected View mSetWallpaperButton;
 
     private HandlerThread mLoaderThread;
@@ -96,7 +81,7 @@
 
         init();
         if (!enableRotation()) {
-            setRequestedOrientation(Configuration.ORIENTATION_PORTRAIT);
+            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
         }
     }
 
@@ -123,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);
@@ -165,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);
                     }
@@ -222,7 +227,7 @@
     }
 
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
-    protected boolean isActivityDestroyed() {
+    public boolean isActivityDestroyed() {
         return Utilities.ATLEAST_JB_MR1 && isDestroyed();
     }
 
@@ -246,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
@@ -264,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
@@ -295,54 +308,39 @@
         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() {
-                updateWallpaperDimensions(bounds.x, bounds.y);
-                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);
+        int rotation = BitmapUtils.getRotationFromExif(res, resId, this);
         Point inSize = mCropView.getSourceDimensions();
         Point outSize = WallpaperUtils.getDefaultWallpaperSize(getResources(),
                 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
-                updateWallpaperDimensions(0, 0);
-                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;
@@ -415,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() {
-                updateWallpaperDimensions(outWidth, outHeight);
-                if (finishActivityWhenDone) {
-                    setResult(Activity.RESULT_OK);
-                    finish();
-                }
-            }
-        };
         BitmapCropTask cropTask = new BitmapCropTask(getContext(), uri,
                 cropRect, cropRotation, outWidth, outHeight, true, false, onEndCrop);
         if (onBitmapCroppedHandler != null) {
@@ -435,20 +427,30 @@
         cropTask.execute();
     }
 
-    protected void updateWallpaperDimensions(int width, int height) {
-        String spKey = LauncherFiles.WALLPAPER_CROP_PREFERENCES_KEY;
-        SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS);
-        SharedPreferences.Editor editor = sp.edit();
-        if (width != 0 && height != 0) {
-            editor.putInt(WALLPAPER_WIDTH_KEY, width);
-            editor.putInt(WALLPAPER_HEIGHT_KEY, height);
-        } else {
-            editor.remove(WALLPAPER_WIDTH_KEY);
-            editor.remove(WALLPAPER_HEIGHT_KEY);
+    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;
         }
-        editor.commit();
-        WallpaperUtils.suggestWallpaperDimension(getResources(),
-                sp, getWindowManager(), WallpaperManager.getInstance(getContext()), true);
+
+        @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 {
@@ -456,12 +458,13 @@
         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 5985850..27d60f8 100644
--- a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
+++ b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
@@ -16,39 +16,22 @@
 
 package com.android.launcher3;
 
-import android.Manifest.permission;
 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.Manifest;
 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;
@@ -56,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.UriBitmapSource bitmapSource =
-                    new BitmapRegionTileSource.UriBitmapSource(a.getContext(), Uri.fromFile(mFile));
-            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.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) {
@@ -371,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);
 
@@ -381,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
@@ -521,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);
@@ -566,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;
@@ -689,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);
@@ -740,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();
@@ -796,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) {
@@ -910,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);
@@ -959,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)));
                     }
                 }
             }
@@ -977,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);
             }
@@ -986,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);
-            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,
@@ -1109,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 2d496a5..2f9c9a3 100644
--- a/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java
+++ b/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java
@@ -31,8 +31,8 @@
 import android.util.Log;
 
 import com.android.gallery3d.common.BitmapUtils;
+import com.android.gallery3d.common.ExifOrientation;
 import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.exif.ExifInterface;
 import com.android.gallery3d.glrenderer.BasicTexture;
 import com.android.gallery3d.glrenderer.BitmapTexture;
 import com.android.photos.views.TiledImageRenderer;
@@ -160,13 +160,7 @@
         private State mState = State.NOT_LOADED;
 
         public boolean loadInBackground(InBitmapProvider bitmapProvider) {
-            ExifInterface ei = new ExifInterface();
-            if (readExif(ei)) {
-                Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION);
-                if (ori != null) {
-                    mRotation = ExifInterface.getRotationForOrientationValue(ori.shortValue());
-                }
-            }
+            mRotation = getExifRotation();
             mDecoder = loadBitmapRegionDecoder();
             if (mDecoder == null) {
                 mState = State.ERROR_LOADING;
@@ -232,7 +226,7 @@
             return mRotation;
         }
 
-        public abstract boolean readExif(ExifInterface ei);
+        public abstract int getExifRotation();
         public abstract SimpleBitmapRegionDecoder loadBitmapRegionDecoder();
         public abstract Bitmap loadPreviewBitmap(BitmapFactory.Options options);
 
@@ -259,18 +253,10 @@
         public Bitmap loadPreviewBitmap(BitmapFactory.Options options) {
             return BitmapFactory.decodeFile(mPath, options);
         }
+
         @Override
-        public boolean readExif(ExifInterface ei) {
-            try {
-                ei.readExif(mPath);
-                return true;
-            } catch (NullPointerException e) {
-                Log.w("BitmapRegionTileSource", "reading exif failed", e);
-                return false;
-            } catch (IOException e) {
-                Log.w("BitmapRegionTileSource", "getting decoder failed", e);
-                return false;
-            }
+        public int getExifRotation() {
+            return ExifOrientation.readRotation(mPath);
         }
     }
 
@@ -315,35 +301,22 @@
                 return null;
             }
         }
+
         @Override
-        public boolean readExif(ExifInterface ei) {
-            InputStream is = null;
-            try {
-                is = regenerateInputStream();
-                ei.readExif(is);
-                Utils.closeSilently(is);
-                return true;
-            } catch (FileNotFoundException e) {
-                Log.d("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
-                return false;
-            } catch (IOException e) {
-                Log.d("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
-                return false;
-            } catch (NullPointerException e) {
-                Log.d("BitmapRegionTileSource", "Failed to read EXIF for URI " + mUri, e);
-                return false;
-            } finally {
-                Utils.closeSilently(is);
-            }
+        public int getExifRotation() {
+            return BitmapUtils.getRotationFromExif(mContext, mUri);
         }
     }
 
     public static class ResourceBitmapSource extends BitmapSource {
         private Resources mRes;
         private int mResId;
-        public ResourceBitmapSource(Resources res, int resId) {
+        private Context mContext;
+
+        public ResourceBitmapSource(Resources res, int resId, Context context) {
             mRes = res;
             mResId = resId;
+            mContext = context;
         }
         private InputStream regenerateInputStream() {
             InputStream is = mRes.openRawResource(mResId);
@@ -366,17 +339,10 @@
         public Bitmap loadPreviewBitmap(BitmapFactory.Options options) {
             return BitmapFactory.decodeResource(mRes, mResId, options);
         }
+
         @Override
-        public boolean readExif(ExifInterface ei) {
-            try {
-                InputStream is = regenerateInputStream();
-                ei.readExif(is);
-                Utils.closeSilently(is);
-                return true;
-            } catch (IOException e) {
-                Log.e("BitmapRegionTileSource", "Error reading resource", e);
-                return false;
-            }
+        public int getExifRotation() {
+            return BitmapUtils.getRotationFromExif(mRes, mResId, mContext);
         }
     }
 
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/proguard.flags b/proguard.flags
index 5a3dfd1..22ffa3c 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -44,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/ic_setting_pressed.png b/res/drawable-hdpi/ic_setting_pressed.png
deleted file mode 100644
index d5b5ca2..0000000
--- a/res/drawable-hdpi/ic_setting_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_wallpaper_pressed.png b/res/drawable-hdpi/ic_wallpaper_pressed.png
deleted file mode 100644
index 1588ce7..0000000
--- a/res/drawable-hdpi/ic_wallpaper_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_widget_pressed.png b/res/drawable-hdpi/ic_widget_pressed.png
deleted file mode 100644
index 19d6fed..0000000
--- a/res/drawable-hdpi/ic_widget_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_setting_pressed.png b/res/drawable-mdpi/ic_setting_pressed.png
deleted file mode 100644
index 61e574a..0000000
--- a/res/drawable-mdpi/ic_setting_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_wallpaper_pressed.png b/res/drawable-mdpi/ic_wallpaper_pressed.png
deleted file mode 100644
index aa598c3..0000000
--- a/res/drawable-mdpi/ic_wallpaper_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_widget_pressed.png b/res/drawable-mdpi/ic_widget_pressed.png
deleted file mode 100644
index 9b690d9..0000000
--- a/res/drawable-mdpi/ic_widget_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_setting_pressed.png b/res/drawable-xhdpi/ic_setting_pressed.png
deleted file mode 100644
index 005d49c..0000000
--- a/res/drawable-xhdpi/ic_setting_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_wallpaper_pressed.png b/res/drawable-xhdpi/ic_wallpaper_pressed.png
deleted file mode 100644
index 5a9b84d..0000000
--- a/res/drawable-xhdpi/ic_wallpaper_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_widget_pressed.png b/res/drawable-xhdpi/ic_widget_pressed.png
deleted file mode 100644
index 633c9c6..0000000
--- a/res/drawable-xhdpi/ic_widget_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_setting_pressed.png b/res/drawable-xxhdpi/ic_setting_pressed.png
deleted file mode 100644
index d0cad5e..0000000
--- a/res/drawable-xxhdpi/ic_setting_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_wallpaper_pressed.png b/res/drawable-xxhdpi/ic_wallpaper_pressed.png
deleted file mode 100644
index e5d200b..0000000
--- a/res/drawable-xxhdpi/ic_wallpaper_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_widget_pressed.png b/res/drawable-xxhdpi/ic_widget_pressed.png
deleted file mode 100644
index b8dd35d..0000000
--- a/res/drawable-xxhdpi/ic_widget_pressed.png
+++ /dev/null
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..f431fb1 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">
@@ -68,7 +68,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..a7f851e 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">
@@ -78,7 +78,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..319a493 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">
@@ -76,7 +76,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/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 fb54f12..f4a4284 100644
--- a/res/values-sw600dp/dimens.xml
+++ b/res/values-sw600dp/dimens.xml
@@ -30,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/attrs.xml b/res/values/attrs.xml
index 827332a..c2017e2 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -75,10 +75,6 @@
     <!-- PagedView specific attributes. These attributes are used to customize
          a PagedView view in XML files. -->
     <declare-styleable name="PagedView">
-        <!-- A spacing override for the icons within a page -->
-        <attr name="pageLayoutWidthGap" format="dimension" />
-        <attr name="pageLayoutHeightGap" format="dimension" />
-
         <!-- The page indicator for this workspace -->
         <attr name="pageIndicator" format="reference" />
     </declare-styleable>
diff --git a/res/values/config.xml b/res/values/config.xml
index 93c6d14..9f65569 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 -->
@@ -74,13 +73,12 @@
          filter the activities shown in the launcher. Can be empty. -->
     <string name="app_filter_class" translatable="false"></string>
 
-    <!-- Name of a subclass of com.android.launcher3.BuildInfo used to
-         get build information. Can be empty. -->
-    <string name="build_info_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 3672179..4882df6 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -89,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>
@@ -115,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
new file mode 100644
index 0000000..eaac6be
--- /dev/null
+++ b/src/com/android/launcher3/AnotherWindowDropTarget.java
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.graphics.Rect;
+
+/**
+ * Drop target used when another window (i.e. another process) has accepted a global system drag.
+ * If the accepted item was a shortcut, we delete it from Launcher.
+ */
+public class AnotherWindowDropTarget implements DropTarget {
+    final Launcher mLauncher;
+
+    public AnotherWindowDropTarget (Context context) { mLauncher = (Launcher) context; }
+
+    @Override
+    public boolean isDropEnabled() { return true; }
+
+    @Override
+    public void onDrop(DragObject dragObject) {
+        dragObject.deferDragViewCleanupPostAnimation = false;
+        LauncherModel.deleteItemFromDatabase(mLauncher, (ShortcutInfo) dragObject.dragInfo);
+    }
+
+    @Override
+    public void onDragEnter(DragObject dragObject) {}
+
+    @Override
+    public void onDragOver(DragObject dragObject) {}
+
+    @Override
+    public void onDragExit(DragObject dragObject) {}
+
+    @Override
+    public void onFlingToDelete(DragObject dragObject, PointF vec) {}
+
+    @Override
+    public boolean acceptDrop(DragObject dragObject) {
+        return dragObject.dragInfo instanceof ShortcutInfo;
+    }
+
+    @Override
+    public void prepareAccessibilityDrop() {}
+
+    // These methods are implemented in Views
+    @Override
+    public void getHitRectRelativeToDragLayer(Rect outRect) {}
+
+    @Override
+    public void getLocationInDragLayer(int[] loc) {}
+
+    @Override
+    public int getLeft() { return 0; }
+
+    @Override
+    public int getTop() { return 0; }
+}
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java
index c95d558..7249c64 100644
--- a/src/com/android/launcher3/AppInfo.java
+++ b/src/com/android/launcher3/AppInfo.java
@@ -28,13 +28,11 @@
 import com.android.launcher3.util.ComponentKey;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 
 /**
  * Represents an app in AllAppsView.
  */
 public class AppInfo extends ItemInfo {
-    private static final String TAG = "Launcher3.AppInfo";
 
     /**
      * The intent used to start the application.
@@ -118,8 +116,7 @@
         return "ApplicationInfo(title=" + title + " id=" + this.id
                 + " type=" + this.itemType + " container=" + this.container
                 + " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY
-                + " spanX=" + spanX + " spanY=" + spanY + " dropPos=" + Arrays.toString(dropPos)
-                + " user=" + user + ")";
+                + " spanX=" + spanX + " spanY=" + spanY + " user=" + user + ")";
     }
 
     /**
@@ -128,7 +125,7 @@
     public static void dumpApplicationInfoList(String tag, String label, ArrayList<AppInfo> list) {
         Log.d(tag, label + " size=" + list.size());
         for (AppInfo info: list) {
-            Log.d(tag, "   title=\"" + info.title + "\" iconBitmap=" + info.iconBitmap 
+            Log.d(tag, "   title=\"" + info.title + "\" iconBitmap=" + info.iconBitmap
                     + " firstInstallTime=" + info.firstInstallTime
                     + " componentName=" + info.componentName.getPackageName());
         }
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index e6bf525..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;
     }
@@ -441,10 +446,10 @@
             PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", lp.y, newY);
             ObjectAnimator oa =
                     LauncherAnimUtils.ofPropertyValuesHolder(lp, this, width, height, x, y);
-            ObjectAnimator leftOa = LauncherAnimUtils.ofFloat(mLeftHandle, "alpha", 1.0f);
-            ObjectAnimator rightOa = LauncherAnimUtils.ofFloat(mRightHandle, "alpha", 1.0f);
-            ObjectAnimator topOa = LauncherAnimUtils.ofFloat(mTopHandle, "alpha", 1.0f);
-            ObjectAnimator bottomOa = LauncherAnimUtils.ofFloat(mBottomHandle, "alpha", 1.0f);
+            ObjectAnimator leftOa = LauncherAnimUtils.ofFloat(mLeftHandle, ALPHA, 1.0f);
+            ObjectAnimator rightOa = LauncherAnimUtils.ofFloat(mRightHandle, ALPHA, 1.0f);
+            ObjectAnimator topOa = LauncherAnimUtils.ofFloat(mTopHandle, ALPHA, 1.0f);
+            ObjectAnimator bottomOa = LauncherAnimUtils.ofFloat(mBottomHandle, ALPHA, 1.0f);
             oa.addUpdateListener(new AnimatorUpdateListener() {
                 public void onAnimationUpdate(ValueAnimator animation) {
                     requestLayout();
diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
index b1d51ec..10fdd87 100644
--- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
+++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
@@ -13,7 +13,6 @@
 import android.util.Log;
 
 import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.compat.AppWidgetManagerCompat;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/src/com/android/launcher3/BaseContainerView.java b/src/com/android/launcher3/BaseContainerView.java
index c118240..e0946ea 100644
--- a/src/com/android/launcher3/BaseContainerView.java
+++ b/src/com/android/launcher3/BaseContainerView.java
@@ -22,6 +22,8 @@
 import android.util.Log;
 import android.widget.LinearLayout;
 
+import com.android.launcher3.config.ProviderConfig;
+
 /**
  * A base container view, which supports resizing.
  */
@@ -71,7 +73,7 @@
      * Sets the search bar bounds for this container view to match.
      */
     final public void setSearchBarBounds(Rect bounds) {
-        if (LauncherAppState.isDogfoodBuild() && !isValidSearchBarBounds(bounds)) {
+        if (ProviderConfig.IS_DOGFOOD_BUILD && !isValidSearchBarBounds(bounds)) {
             Log.e(TAG, "Invalid search bar bounds: " + bounds);
         }
 
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/BuildInfo.java b/src/com/android/launcher3/BuildInfo.java
index b49ee0d..1392d7a 100644
--- a/src/com/android/launcher3/BuildInfo.java
+++ b/src/com/android/launcher3/BuildInfo.java
@@ -1,32 +1,9 @@
 package com.android.launcher3;
 
-import android.text.TextUtils;
-import android.util.Log;
-
+// TODO: Remove this class once all its references are gone.
 public class BuildInfo {
-    private static final boolean DBG = false;
-    private static final String TAG = "BuildInfo";
 
     public boolean isDogfoodBuild() {
         return false;
     }
-
-    public static BuildInfo loadByName(String className) {
-        if (TextUtils.isEmpty(className)) return new BuildInfo();
-
-        if (DBG) Log.d(TAG, "Loading BuildInfo: " + className);
-        try {
-            Class<?> cls = Class.forName(className);
-            return (BuildInfo) cls.newInstance();
-        } catch (ClassNotFoundException e) {
-            Log.e(TAG, "Bad BuildInfo class", e);
-        } catch (InstantiationException e) {
-            Log.e(TAG, "Bad BuildInfo class", e);
-        } catch (IllegalAccessException e) {
-            Log.e(TAG, "Bad BuildInfo class", e);
-        } catch (ClassCastException e) {
-            Log.e(TAG, "Bad BuildInfo class", e);
-        }
-        return new BuildInfo();
-    }
 }
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index 2baa6d8..dc29f7d 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;
 
 /**
@@ -184,8 +187,8 @@
         }
     }
 
-	@Override
-    public final void onDragStart(DragSource source, Object info, int dragAction) {
+    @Override
+    public final void onDragStart(DragSource source, ItemInfo info, int dragAction) {
         mActive = supportsDrop(source, info);
         mDrawable.setColorFilter(null);
         if (mCurrentColorAnim != null) {
@@ -201,7 +204,7 @@
         return supportsDrop(dragObject.dragSource, dragObject.dragInfo);
     }
 
-    protected abstract boolean supportsDrop(DragSource source, Object info);
+    protected abstract boolean supportsDrop(DragSource source, ItemInfo info);
 
     @Override
     public boolean isDropEnabled() {
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 84e2d49..653ee7e 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -18,14 +18,12 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 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;
@@ -37,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;
@@ -47,12 +46,16 @@
 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;
+import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
 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;
@@ -84,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.
@@ -187,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;
@@ -201,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;
 
@@ -276,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);
@@ -338,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;
@@ -404,7 +404,7 @@
         mIsDragTarget = false;
     }
 
-    boolean isDragTarget() {
+    public boolean isDragTarget() {
         return mIsDragTarget;
     }
 
@@ -424,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;
     }
 
@@ -447,12 +473,9 @@
         for (int i = 0; i < mDragOutlines.length; i++) {
             final float alpha = mDragOutlineAlphas[i];
             if (alpha > 0) {
-                final Rect r = mDragOutlines[i];
-                mTempRect.set(r);
-                Utilities.scaleRectAboutCenter(mTempRect, getChildrenScale());
                 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
                 paint.setAlpha((int)(alpha + .5f));
-                canvas.drawBitmap(b, null, mTempRect, paint);
+                canvas.drawBitmap(b, null, mDragOutlines[i], paint);
             }
         }
 
@@ -567,7 +590,7 @@
         try {
             dispatchRestoreInstanceState(states);
         } catch (IllegalArgumentException ex) {
-            if (LauncherAppState.isDogfoodBuild()) {
+            if (ProviderConfig.IS_DOGFOOD_BUILD) {
                 throw ex;
             }
             // Mismatched viewId / viewType preventing restore. Skip restore on production builds.
@@ -1030,53 +1053,57 @@
         if (cellX != oldDragCellX || cellY != oldDragCellY) {
             mDragCell[0] = cellX;
             mDragCell[1] = cellY;
-            // Find the top left corner of the rect the object will occupy
-            final int[] topLeft = mTmpPoint;
-            cellToPoint(cellX, cellY, topLeft);
 
-            int left = topLeft[0];
-            int top = topLeft[1];
-
-            if (v != null && dragOffset == null) {
-                // When drawing the drag outline, it did not account for margin offsets
-                // added by the view's parent.
-                MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
-                left += lp.leftMargin;
-                top += lp.topMargin;
-
-                // Offsets due to the size difference between the View and the dragOutline.
-                // There is a size difference to account for the outer blur, which may lie
-                // outside the bounds of the view.
-                top += (v.getHeight() - dragOutline.getHeight()) / 2;
-                // We center about the x axis
-                left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
-                        - dragOutline.getWidth()) / 2;
-            } else {
-                if (dragOffset != null && dragRegion != null) {
-                    // Center the drag region *horizontally* in the cell and apply a drag
-                    // outline offset
-                    left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
-                             - dragRegion.width()) / 2;
-                    int cHeight = getShortcutsAndWidgets().getCellContentHeight();
-                    int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
-                    top += dragOffset.y + cellPaddingY;
-                } else {
-                    // Center the drag outline in the cell
-                    left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
-                            - dragOutline.getWidth()) / 2;
-                    top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
-                            - dragOutline.getHeight()) / 2;
-                }
-            }
             final int oldIndex = mDragOutlineCurrent;
             mDragOutlineAnims[oldIndex].animateOut();
             mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
             Rect r = mDragOutlines[mDragOutlineCurrent];
-            r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
+
             if (resize) {
                 cellToRect(cellX, cellY, spanX, spanY, r);
+            } else {
+                // Find the top left corner of the rect the object will occupy
+                final int[] topLeft = mTmpPoint;
+                cellToPoint(cellX, cellY, topLeft);
+
+                int left = topLeft[0];
+                int top = topLeft[1];
+
+                if (v != null && dragOffset == null) {
+                    // When drawing the drag outline, it did not account for margin offsets
+                    // added by the view's parent.
+                    MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
+                    left += lp.leftMargin;
+                    top += lp.topMargin;
+
+                    // Offsets due to the size difference between the View and the dragOutline.
+                    // There is a size difference to account for the outer blur, which may lie
+                    // outside the bounds of the view.
+                    top += (v.getHeight() - dragOutline.getHeight()) / 2;
+                    // We center about the x axis
+                    left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
+                            - dragOutline.getWidth()) / 2;
+                } else {
+                    if (dragOffset != null && dragRegion != null) {
+                        // Center the drag region *horizontally* in the cell and apply a drag
+                        // outline offset
+                        left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
+                                - dragRegion.width()) / 2;
+                        int cHeight = getShortcutsAndWidgets().getCellContentHeight();
+                        int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
+                        top += dragOffset.y + cellPaddingY;
+                    } else {
+                        // Center the drag outline in the cell
+                        left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
+                                - dragOutline.getWidth()) / 2;
+                        top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
+                                - dragOutline.getHeight()) / 2;
+                    }
+                }
+                r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
             }
 
+            Utilities.scaleRectAboutCenter(r, getChildrenScale());
             mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
             mDragOutlineAnims[mDragOutlineCurrent].animateIn();
         }
@@ -1094,23 +1121,6 @@
      *
      * @param pixelX The X location at which you want to search for a vacant area.
      * @param pixelY The Y location at which you want to search for a vacant area.
-     * @param spanX Horizontal span of the object.
-     * @param spanY Vertical span of the object.
-     * @param result Array in which to place the result, or null (in which case a new array will
-     *        be allocated)
-     * @return The X, Y cell of a vacant area that can contain this object,
-     *         nearest the requested location.
-     */
-    int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
-        return findNearestVacantArea(pixelX, pixelY, spanX, spanY, spanX, spanY, result, null);
-    }
-
-    /**
-     * Find a vacant area that will fit the given bounds nearest the requested
-     * cell location. Uses Euclidean distance to score multiple vacant areas.
-     *
-     * @param pixelX The X location at which you want to search for a vacant area.
-     * @param pixelY The Y location at which you want to search for a vacant area.
      * @param minSpanX The minimum horizontal span required
      * @param minSpanY The minimum vertical span required
      * @param spanX Horizontal span of the object.
@@ -2133,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() {
@@ -2175,17 +2195,14 @@
                 a.cancel();
             }
 
-            AnimatorSet s = LauncherAnimUtils.createAnimatorSet();
-            a = s;
-            s.playTogether(
-                LauncherAnimUtils.ofFloat(child, "scaleX", getChildrenScale()),
-                LauncherAnimUtils.ofFloat(child, "scaleY", getChildrenScale()),
-                LauncherAnimUtils.ofFloat(child, "translationX", 0f),
-                LauncherAnimUtils.ofFloat(child, "translationY", 0f)
-            );
-            s.setDuration(REORDER_ANIMATION_DURATION);
-            s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
-            s.start();
+            a = new LauncherViewPropertyAnimator(child)
+                .scaleX(getChildrenScale())
+                .scaleY(getChildrenScale())
+                .translationX(0)
+                .translationY(0)
+                .setDuration(REORDER_ANIMATION_DURATION);
+            a.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
+            a.start();
         }
     }
 
@@ -2202,6 +2219,15 @@
                 mOccupied[i][j] = mTmpOccupied[i][j];
             }
         }
+
+        long screenId = mLauncher.getWorkspace().getIdForScreen(this);
+        int container = Favorites.CONTAINER_DESKTOP;
+
+        if (mLauncher.isHotseatLayout(this)) {
+            screenId = -1;
+            container = Favorites.CONTAINER_HOTSEAT;
+        }
+
         int childCount = mShortcutsAndWidgets.getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = mShortcutsAndWidgets.getChildAt(i);
@@ -2210,17 +2236,21 @@
             // We do a null check here because the item info can be null in the case of the
             // AllApps button in the hotseat.
             if (info != null) {
-                if (info.cellX != lp.tmpCellX || info.cellY != lp.tmpCellY ||
-                        info.spanX != lp.cellHSpan || info.spanY != lp.cellVSpan) {
-                    info.requiresDbUpdate = true;
-                }
+                final boolean requiresDbUpdate = (info.cellX != lp.tmpCellX
+                        || info.cellY != lp.tmpCellY || info.spanX != lp.cellHSpan
+                        || info.spanY != lp.cellVSpan);
+
                 info.cellX = lp.cellX = lp.tmpCellX;
                 info.cellY = lp.cellY = lp.tmpCellY;
                 info.spanX = lp.cellHSpan;
                 info.spanY = lp.cellVSpan;
+
+                if (requiresDbUpdate) {
+                    LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId,
+                            info.cellX, info.cellY, info.spanX, info.spanY);
+                }
             }
         }
-        mLauncher.getWorkspace().updateItemLocationsInDatabase(this);
     }
 
     private void setUseTempCoords(boolean useTempCoords) {
@@ -2807,10 +2837,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;
 
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 9c8659c..32bf192 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -24,6 +24,7 @@
 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;
 
@@ -46,20 +47,20 @@
         setDrawable(R.drawable.ic_remove_launcher);
     }
 
-    public static boolean supportsDrop(Object info) {
+    public static boolean supportsDrop(ItemInfo info) {
         return (info instanceof ShortcutInfo)
                 || (info instanceof LauncherAppWidgetInfo)
                 || (info instanceof FolderInfo);
     }
 
     @Override
-    protected boolean supportsDrop(DragSource source, Object info) {
+    protected boolean supportsDrop(DragSource source, ItemInfo info) {
         return source.supportsDeleteDropTarget() && supportsDrop(info);
     }
 
     @Override
     @Thunk void completeDrop(DragObject d) {
-        ItemInfo item = (ItemInfo) d.dragInfo;
+        ItemInfo item = d.dragInfo;
         if ((d.dragSource instanceof Workspace) || (d.dragSource instanceof Folder)) {
             removeWorkspaceOrFolderItem(mLauncher, item, null);
         }
@@ -111,7 +112,6 @@
     public void onFlingToDelete(final DragObject d, PointF vel) {
         // Don't highlight the icon as it's animating
         d.dragView.setColor(0);
-        d.dragView.updateInitialScaleToCurrentScale();
 
         final DragLayer dragLayer = mLauncher.getDragLayer();
         FlingAnimation fling = new FlingAnimation(d, vel,
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 774594f..6364d90 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);
@@ -339,13 +351,13 @@
     }
 
     // The rect returned will be extended to below the system ui that covers the workspace
-    Rect getHotseatRect() {
+    public boolean isInHotseatRect(int x, int y) {
         if (isVerticalBarLayout()) {
-            return new Rect(availableWidthPx - hotseatBarHeightPx, 0,
-                    Integer.MAX_VALUE, availableHeightPx);
+            return (x >= (availableWidthPx - hotseatBarHeightPx))
+                    && (y >= 0) && (y <= availableHeightPx);
         } else {
-            return new Rect(0, availableHeightPx - hotseatBarHeightPx,
-                    availableWidthPx, Integer.MAX_VALUE);
+            return (x >= 0) && (x <= availableWidthPx)
+                    && (y >= (availableHeightPx - hotseatBarHeightPx));
         }
     }
 
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index c8fac54..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;
 
@@ -47,7 +49,7 @@
         public DragView dragView = null;
 
         /** The data associated with the object being dragged */
-        public Object dragInfo = null;
+        public ItemInfo dragInfo = null;
 
         /** Where the drag originated */
         public DragSource dragSource = null;
diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java
index c7b64ec..bf4551b 100644
--- a/src/com/android/launcher3/ExtendedEditText.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -17,6 +17,7 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
+import android.view.DragEvent;
 import android.view.KeyEvent;
 import android.widget.EditText;
 
@@ -62,4 +63,10 @@
         }
         return super.onKeyPreIme(keyCode, event);
     }
+
+    @Override
+    public boolean onDragEvent(DragEvent event) {
+        // We don't want this view to interfere with Launcher own drag and drop.
+        return false;
+    }
 }
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
index 57aec32..3751b88 100644
--- a/src/com/android/launcher3/FocusHelper.java
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -22,6 +22,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.launcher3.config.ProviderConfig;
 import com.android.launcher3.util.FocusLogic;
 import com.android.launcher3.util.Thunk;
 
@@ -74,7 +75,7 @@
 
 
             if (!(v.getParent() instanceof ShortcutAndWidgetContainer)) {
-                if (LauncherAppState.isDogfoodBuild()) {
+                if (ProviderConfig.IS_DOGFOOD_BUILD) {
                     throw new IllegalStateException("Parent of the focused item is not supported.");
                 } else {
                     return false;
diff --git a/src/com/android/launcher3/FocusIndicatorView.java b/src/com/android/launcher3/FocusIndicatorView.java
index ecf93e4..2337f6e 100644
--- a/src/com/android/launcher3/FocusIndicatorView.java
+++ b/src/com/android/launcher3/FocusIndicatorView.java
@@ -16,7 +16,7 @@
 
 package com.android.launcher3;
 
-import android.animation.ObjectAnimator;
+import android.animation.Animator;
 import android.animation.PropertyValuesHolder;
 import android.content.Context;
 import android.graphics.Canvas;
@@ -36,7 +36,7 @@
     private final int[] mIndicatorPos = new int[2];
     private final int[] mTargetViewPos = new int[2];
 
-    private ObjectAnimator mCurrentAnimation;
+    private Animator mCurrentAnimation;
     private ViewAnimState mTargetState;
 
     private View mLastFocusedView;
@@ -98,12 +98,12 @@
 
             if (getAlpha() > MIN_VISIBLE_ALPHA) {
                 mTargetState = nextState;
-                mCurrentAnimation = LauncherAnimUtils.ofPropertyValuesHolder(this,
-                        PropertyValuesHolder.ofFloat(View.ALPHA, 1),
-                        PropertyValuesHolder.ofFloat(View.TRANSLATION_X, mTargetState.x),
-                        PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, mTargetState.y),
-                        PropertyValuesHolder.ofFloat(View.SCALE_X, mTargetState.scaleX),
-                        PropertyValuesHolder.ofFloat(View.SCALE_Y, mTargetState.scaleY));
+                mCurrentAnimation = new LauncherViewPropertyAnimator(this)
+                        .alpha(1)
+                        .translationX(mTargetState.x)
+                        .translationY(mTargetState.y)
+                        .scaleX(mTargetState.scaleX)
+                        .scaleY(mTargetState.scaleY);
             } else {
                 applyState(nextState);
                 mCurrentAnimation = LauncherAnimUtils.ofPropertyValuesHolder(this,
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index c1aa356..1a3876d 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
      */
@@ -457,11 +475,7 @@
             positionAndSizeAsIcon();
             centerAboutIcon();
 
-            PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1);
-            PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
-            PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
-            final ObjectAnimator oa =
-                LauncherAnimUtils.ofPropertyValuesHolder(this, alpha, scaleX, scaleY);
+            final ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(this, 1, 1, 1);
             oa.setDuration(mExpandDuration);
             openFolderAnim = oa;
 
@@ -484,8 +498,8 @@
             float transY = - 0.075f * (height / 2 - getPivotY());
             setTranslationX(transX);
             setTranslationY(transY);
-            PropertyValuesHolder tx = PropertyValuesHolder.ofFloat("translationX", transX, 0);
-            PropertyValuesHolder ty = PropertyValuesHolder.ofFloat("translationY", transY, 0);
+            PropertyValuesHolder tx = PropertyValuesHolder.ofFloat(TRANSLATION_X, transX, 0);
+            PropertyValuesHolder ty = PropertyValuesHolder.ofFloat(TRANSLATION_Y, transY, 0);
 
             Animator drift = ObjectAnimator.ofPropertyValuesHolder(this, tx, ty);
             drift.setDuration(mMaterialExpandDuration);
@@ -564,6 +578,7 @@
             final boolean updateAnimationFlag = !mDragInProgress;
             openFolderAnim.addListener(new AnimatorListenerAdapter() {
 
+                @SuppressLint("InlinedApi")
                 @Override
                 public void onAnimationEnd(Animator animation) {
                     mFolderName.animate().setDuration(FOLDER_NAME_ANIMATION_DURATION)
@@ -607,7 +622,7 @@
     }
 
     @Override
-    public void onDragStart(DragSource source, Object info, int dragAction) { }
+    public void onDragStart(DragSource source, ItemInfo info, int dragAction) { }
 
     @Override
     public void onDragEnd() {
@@ -630,12 +645,7 @@
 
     public void animateClosed() {
         if (!(getParent() instanceof DragLayer)) return;
-        PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
-        PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 0.9f);
-        PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 0.9f);
-        final ObjectAnimator oa =
-                LauncherAnimUtils.ofPropertyValuesHolder(this, alpha, scaleX, scaleY);
-
+        final ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(this, 0, 0.9f, 0.9f);
         oa.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
@@ -656,7 +666,7 @@
     }
 
     public boolean acceptDrop(DragObject d) {
-        final ItemInfo item = (ItemInfo) d.dragInfo;
+        final ItemInfo item = d.dragInfo;
         final int itemType = item.itemType;
         return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
                     itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) &&
diff --git a/src/com/android/launcher3/FolderIcon.java b/src/com/android/launcher3/FolderIcon.java
index 8d534d2..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);
@@ -330,8 +331,8 @@
                 !mFolder.isFull() && item != mInfo && !mInfo.opened);
     }
 
-    public boolean acceptDrop(Object dragInfo) {
-        final ItemInfo item = (ItemInfo) dragInfo;
+    public boolean acceptDrop(ItemInfo dragInfo) {
+        final ItemInfo item = dragInfo;
         return !mFolder.isDestroyed() && willAcceptItem(item);
     }
 
@@ -339,8 +340,8 @@
         mInfo.add(item);
     }
 
-    public void onDragEnter(Object dragInfo) {
-        if (mFolder.isDestroyed() || !willAcceptItem((ItemInfo) dragInfo)) return;
+    public void onDragEnter(ItemInfo dragInfo) {
+        if (mFolder.isDestroyed() || !willAcceptItem(dragInfo)) return;
         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
         CellLayout layout = (CellLayout) getParent().getParent();
         mFolderRingAnimator.setCell(lp.cellX, lp.cellY);
@@ -356,7 +357,7 @@
             // Workspace#onDropExternal.
             mOpenAlarm.setAlarm(ON_OPEN_DELAY);
         }
-        mDragInfo = (ItemInfo) dragInfo;
+        mDragInfo = dragInfo;
     }
 
     public void onDragOver(Object dragInfo) {
@@ -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/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index aea21c9..6e0dcd4 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -22,7 +22,6 @@
 import com.android.launcher3.compat.UserHandleCompat;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 
 /**
  * Represents a folder containing shortcuts or apps.
@@ -140,7 +139,7 @@
         return "FolderInfo(id=" + this.id + " type=" + this.itemType
                 + " container=" + this.container + " screen=" + screenId
                 + " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX
-                + " spanY=" + spanY + " dropPos=" + Arrays.toString(dropPos) + ")";
+                + " spanY=" + spanY + ")";
     }
 
     public boolean hasOption(int optionFlag) {
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/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 17fdeb1..a9cff5e 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -17,8 +17,6 @@
 package com.android.launcher3;
 
 import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.util.AttributeSet;
diff --git a/src/com/android/launcher3/InfoDropTarget.java b/src/com/android/launcher3/InfoDropTarget.java
index d93cdcc..b4a1445 100644
--- a/src/com/android/launcher3/InfoDropTarget.java
+++ b/src/com/android/launcher3/InfoDropTarget.java
@@ -20,8 +20,6 @@
 import android.content.Context;
 import android.util.AttributeSet;
 
-import com.android.launcher3.compat.UserHandleCompat;
-
 public class InfoDropTarget extends ButtonDropTarget {
 
     public InfoDropTarget(Context context, AttributeSet attrs) {
@@ -41,7 +39,7 @@
         setDrawable(R.drawable.ic_info_launcher);
     }
 
-    public static void startDetailsActivityForInfo(Object info, Launcher launcher) {
+    public static void startDetailsActivityForInfo(ItemInfo info, Launcher launcher) {
         ComponentName componentName = null;
         if (info instanceof AppInfo) {
             componentName = ((AppInfo) info).componentName;
@@ -50,24 +48,17 @@
         } else if (info instanceof PendingAddItemInfo) {
             componentName = ((PendingAddItemInfo) info).componentName;
         }
-        final UserHandleCompat user;
-        if (info instanceof ItemInfo) {
-            user = ((ItemInfo) info).user;
-        } else {
-            user = UserHandleCompat.myUserHandle();
-        }
-
         if (componentName != null) {
-            launcher.startApplicationDetailsActivity(componentName, user);
+            launcher.startApplicationDetailsActivity(componentName, info.user);
         }
     }
 
     @Override
-    protected boolean supportsDrop(DragSource source, Object info) {
+    protected boolean supportsDrop(DragSource source, ItemInfo info) {
         return source.supportsAppInfoDropTarget() && supportsDrop(getContext(), info);
     }
 
-    public static boolean supportsDrop(Context context, Object info) {
+    public static boolean supportsDrop(Context context, ItemInfo info) {
         return info instanceof AppInfo || info instanceof PendingAddItemInfo;
     }
 
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index f7e0ea4..aa5a18d 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -24,8 +24,6 @@
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 
-import java.util.Arrays;
-
 /**
  * Represents an item in the launcher.
  */
@@ -35,14 +33,14 @@
      * Intent extra to store the profile. Format: UserHandle
      */
     static final String EXTRA_PROFILE = "profile";
-    
+
     public static final int NO_ID = -1;
-    
+
     /**
      * The id in the settings database for this item
      */
     public long id = NO_ID;
-    
+
     /**
      * One of {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION},
      * {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT},
@@ -50,20 +48,20 @@
      * {@link LauncherSettings.Favorites#ITEM_TYPE_APPWIDGET}.
      */
     public int itemType;
-    
+
     /**
-     * The id of the container that holds this item. For the desktop, this will be 
+     * The id of the container that holds this item. For the desktop, this will be
      * {@link LauncherSettings.Favorites#CONTAINER_DESKTOP}. For the all applications folder it
      * will be {@link #NO_ID} (since it is not stored in the settings DB). For user folders
      * it will be the id of the folder.
      */
     public long container = NO_ID;
-    
+
     /**
      * Iindicates the screen in which the shortcut appears.
      */
     public long screenId = -1;
-    
+
     /**
      * Indicates the X position of the associated cell.
      */
@@ -100,11 +98,6 @@
     public int rank = 0;
 
     /**
-     * Indicates that this item needs to be updated in the db
-     */
-    public boolean requiresDbUpdate = false;
-
-    /**
      * Title of the item
      */
     public CharSequence title;
@@ -114,11 +107,6 @@
      */
     public CharSequence contentDescription;
 
-    /**
-     * The position of the item in a drag-and-drop operation.
-     */
-    public int[] dropPos = null;
-
     public UserHandleCompat user;
 
     public ItemInfo() {
@@ -146,18 +134,11 @@
     }
 
     public Intent getIntent() {
-        throw new RuntimeException("Unexpected Intent");
+        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);
@@ -165,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);
 
@@ -194,7 +196,6 @@
     public String toString() {
         return "Item(id=" + this.id + " type=" + this.itemType + " container=" + this.container
             + " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX
-            + " spanY=" + spanY + " dropPos=" + Arrays.toString(dropPos)
-            + " user=" + user + ")";
+            + " spanY=" + spanY + " user=" + user + ")";
     }
 }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 7ad858c..fd744dc 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -20,7 +20,6 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
 import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
@@ -36,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;
@@ -74,7 +74,6 @@
 import android.view.Gravity;
 import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
-import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MotionEvent;
 import android.view.Surface;
@@ -103,6 +102,10 @@
 import com.android.launcher3.compat.LauncherAppsCompat;
 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;
@@ -123,7 +126,6 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Default launcher application.
@@ -131,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;
@@ -162,8 +164,6 @@
      */
     protected static final int REQUEST_LAST = 100;
 
-    static final int SCREEN_COUNT = 5;
-
     // To turn on these properties, type
     // adb shell setprop log.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
     static final String DUMP_STATE_PROPERTY = "launcher_dump_state";
@@ -176,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";
@@ -227,9 +215,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;
@@ -238,8 +223,6 @@
     private final BroadcastReceiver mCloseSystemDialogsReceiver
             = new CloseSystemDialogsIntentReceiver();
 
-    private LayoutInflater mInflater;
-
     @Thunk Workspace mWorkspace;
     private View mLauncherView;
     private View mPageIndicators;
@@ -250,7 +233,7 @@
     private AppWidgetManagerCompat mAppWidgetManager;
     private LauncherAppWidgetHost mAppWidgetHost;
 
-    @Thunk ItemInfo mPendingAddInfo = new ItemInfo();
+    @Thunk final ItemInfo mPendingAddInfo = new ItemInfo();
     private LauncherAppWidgetProviderInfo mPendingAddWidgetInfo;
     private int mPendingAddWidgetId = -1;
 
@@ -271,7 +254,6 @@
     @Thunk WidgetsContainerView mWidgetsView;
     @Thunk WidgetsModel mWidgetsModel;
 
-    private boolean mAutoAdvanceRunning = false;
     private AppWidgetHostView mQsb;
 
     private Bundle mSavedState;
@@ -292,8 +274,6 @@
     private ArrayList<Runnable> mBindOnResumeCallbacks = new ArrayList<Runnable>();
     private ArrayList<Runnable> mOnResumeCallbacks = new ArrayList<Runnable>();
 
-    private Bundle mSavedInstanceState;
-
     private LauncherModel mModel;
     private IconCache mIconCache;
     @Thunk boolean mUserPresent = true;
@@ -309,16 +289,17 @@
 
     // 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;
 
@@ -438,7 +419,6 @@
         mIconCache = app.getIconCache();
 
         mDragController = new DragController(this);
-        mInflater = getLayoutInflater();
         mStateTransitionAnimation = new LauncherStateTransitionAnimation(this);
 
         mStats = new Stats(this);
@@ -495,7 +475,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,
@@ -643,44 +623,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 !isWorkspaceLoading();
     }
 
-    @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;
-                }
-            }
-        }
-    }
-
     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;
     }
 
     /**
@@ -832,24 +786,22 @@
             return;
         }
 
-        // The pattern used here is that a user PICKs a specific application,
-        // which, depending on the target, might need to CREATE the actual target.
-
-        // For example, the user would PICK_SHORTCUT for "Music playlist", and we
-        // launch over to the Music app to actually CREATE_SHORTCUT.
-        if (resultCode == RESULT_OK && mPendingAddInfo.container != ItemInfo.NO_ID) {
-            final PendingAddArguments args = preparePendingAddArgs(requestCode, data, -1,
-                    mPendingAddInfo);
-            if (isWorkspaceLocked()) {
-                sPendingAddItem = args;
-            } else {
-                completeAdd(args);
+        if (requestCode == REQUEST_CREATE_SHORTCUT) {
+            // Handle custom shortcuts created using ACTION_CREATE_SHORTCUT.
+            if (resultCode == RESULT_OK && mPendingAddInfo.container != ItemInfo.NO_ID) {
+                final PendingAddArguments args = preparePendingAddArgs(requestCode, data, -1,
+                        mPendingAddInfo);
+                if (isWorkspaceLocked()) {
+                    sPendingAddItem = args;
+                } else {
+                    completeAdd(args);
+                    mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
+                            ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
+                }
+            } else if (resultCode == RESULT_CANCELED) {
                 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
                         ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
             }
-        } else if (resultCode == RESULT_CANCELED) {
-            mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
-                    ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
         }
         mDragLayer.clearAnimatedView();
 
@@ -1301,7 +1253,6 @@
      *
      * @param savedState The previous state.
      */
-    @SuppressWarnings("unchecked")
     private void restoreState(Bundle savedState) {
         if (savedState == null) {
             return;
@@ -1318,16 +1269,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 ?
@@ -1337,9 +1281,6 @@
             setWaitingForResult(true);
             mRestoring = true;
         }
-
-        mItemIdToViewId = (HashMap<Integer, Integer>)
-                savedState.getSerializable(RUNTIME_STATE_VIEW_IDS);
     }
 
     /**
@@ -1369,38 +1310,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);
@@ -1486,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);
@@ -1503,7 +1454,6 @@
     private void completeAddShortcut(Intent data, long container, long screenId, int cellX,
             int cellY) {
         int[] cellXY = mTmpAddItemCellCoordinates;
-        int[] touchXY = mPendingAddInfo.dropPos;
         CellLayout layout = getCellLayout(container, screenId);
 
         ShortcutInfo info = InstallShortcutReceiver.fromShortcutIntent(this, data);
@@ -1530,10 +1480,6 @@
                     true)) {
                 return;
             }
-        } else if (touchXY != null) {
-            // when dragging and dropping, just find the closest free spot
-            int[] result = layout.findNearestVacantArea(touchXY[0], touchXY[1], 1, 1, cellXY);
-            foundCellSpan = (result != null);
         } else {
             foundCellSpan = layout.findCellForSpan(cellXY, 1, 1);
         }
@@ -1740,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);
@@ -1761,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() {
@@ -1771,7 +1717,7 @@
                     }
                     i++;
                 }
-                sendAdvanceMessage(mAdvanceInterval);
+                sendAdvanceMessage(ADVANCE_INTERVAL);
             }
             return true;
         }
@@ -1868,8 +1814,6 @@
         boolean alreadyOnHome = mHasFocus && ((intent.getFlags() &
                 Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
                 != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
-        boolean moveToDefaultScreen = mLauncherCallbacks != null ?
-                mLauncherCallbacks.shouldMoveToDefaultScreenOnHomeIntent() : true;
         boolean isActionMain = Intent.ACTION_MAIN.equals(intent.getAction());
         if (isActionMain) {
             // also will cancel mWaitingForResult.
@@ -1923,6 +1867,8 @@
         // as slow logic in the callbacks eat into the time the scroller expects for the snapToPage
         // animation.
         if (isActionMain) {
+            boolean moveToDefaultScreen = mLauncherCallbacks != null ?
+                    mLauncherCallbacks.shouldMoveToDefaultScreenOnHomeIntent() : true;
             if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() &&
                     openFolder == null && moveToDefaultScreen) {
                 mWorkspace.post(new Runnable() {
@@ -1962,19 +1908,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);
@@ -2222,7 +2164,6 @@
         mPendingAddInfo.cellX = mPendingAddInfo.cellY = -1;
         mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1;
         mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = 1;
-        mPendingAddInfo.dropPos = null;
     }
 
     void addAppWidgetImpl(final int appWidgetId, final ItemInfo info, final
@@ -2294,7 +2235,6 @@
         resetAddInfo();
         mPendingAddInfo.container = container;
         mPendingAddInfo.screenId = screenId;
-        mPendingAddInfo.dropPos = null;
 
         if (cell != null) {
             mPendingAddInfo.cellX = cell[0];
@@ -2318,7 +2258,6 @@
         resetAddInfo();
         mPendingAddInfo.container = info.container = container;
         mPendingAddInfo.screenId = info.screenId = screenId;
-        mPendingAddInfo.dropPos = null;
         mPendingAddInfo.minSpanX = info.minSpanX;
         mPendingAddInfo.minSpanY = info.minSpanY;
 
@@ -2478,8 +2417,10 @@
 
         if (v instanceof CellLayout) {
             if (mWorkspace.isInOverviewMode()) {
-                showWorkspace(mWorkspace.indexOfChild(v), true);
+                mWorkspace.snapToPageFromOverView(mWorkspace.indexOfChild(v));
+                showWorkspace(true);
             }
+            return;
         }
 
         Object tag = v.getTag();
@@ -2747,7 +2688,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) {
@@ -3007,10 +2951,6 @@
 
     private void growAndFadeOutFolderIcon(FolderIcon fi) {
         if (fi == null) return;
-        PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
-        PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.5f);
-        PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.5f);
-
         FolderInfo info = (FolderInfo) fi.getTag();
         if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
             CellLayout cl = (CellLayout) fi.getParent().getParent();
@@ -3022,8 +2962,8 @@
         copyFolderIconToImage(fi);
         fi.setVisibility(View.INVISIBLE);
 
-        ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
-                scaleX, scaleY);
+        ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(
+                mFolderIconImageView, 0, 1.5f, 1.5f);
         if (Utilities.ATLEAST_LOLLIPOP) {
             oa.setInterpolator(new LogDecelerateInterpolator(100, 0));
         }
@@ -3033,17 +2973,12 @@
 
     private void shrinkAndFadeInFolderIcon(final FolderIcon fi) {
         if (fi == null) return;
-        PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f);
-        PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
-        PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
-
         final CellLayout cl = (CellLayout) fi.getParent().getParent();
 
         // We remove and re-draw the FolderIcon in-case it has changed
         mDragLayer.removeView(mFolderIconImageView);
         copyFolderIconToImage(fi);
-        ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
-                scaleX, scaleY);
+        ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(mFolderIconImageView, 1, 1, 1);
         oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
         oa.addListener(new AnimatorListenerAdapter() {
             @Override
@@ -3262,38 +3197,17 @@
         }
     }
 
-    /**
-     * @return whether or not the Launcher state changed.
-     */
     public boolean showWorkspace(boolean animated) {
-        return showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated, null);
+        return showWorkspace(animated, null);
     }
 
-    /**
-     * @return whether or not the Launcher state changed.
-     */
     public boolean showWorkspace(boolean animated, Runnable onCompleteRunnable) {
-        return showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated,
-                onCompleteRunnable);
-    }
-
-    /**
-     * @return whether or not the Launcher state changed.
-     */
-    protected boolean showWorkspace(int snapToPage, boolean animated) {
-        return showWorkspace(snapToPage, animated, null);
-    }
-
-    /**
-     * @return whether or not the Launcher state changed.
-     */
-    boolean showWorkspace(int snapToPage, boolean animated, Runnable onCompleteRunnable) {
         boolean changed = mState != State.WORKSPACE ||
                 mWorkspace.getState() != Workspace.State.NORMAL;
         if (changed) {
             mWorkspace.setVisibility(View.VISIBLE);
             mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
-                    Workspace.State.NORMAL, snapToPage, animated, onCompleteRunnable);
+                    Workspace.State.NORMAL, animated, onCompleteRunnable);
 
             // Set focus to the AppsCustomize button
             if (mAllAppsButton != null) {
@@ -3319,9 +3233,7 @@
     void showOverviewMode(boolean animated) {
         mWorkspace.setVisibility(View.VISIBLE);
         mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
-                Workspace.State.OVERVIEW,
-                WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated,
-                null /* onCompleteRunnable */);
+                Workspace.State.OVERVIEW, animated, null /* onCompleteRunnable */);
         mState = State.WORKSPACE;
     }
 
@@ -3398,14 +3310,26 @@
      * Updates the workspace and interaction state on state change, and return the animation to this
      * new state.
      */
-    public Animator startWorkspaceStateChangeAnimation(Workspace.State toState, int toPage,
+    public Animator startWorkspaceStateChangeAnimation(Workspace.State toState,
             boolean animated, HashMap<View, Integer> layerViews) {
         Workspace.State fromState = mWorkspace.getState();
-        Animator anim = mWorkspace.setStateWithAnimation(toState, toPage, animated, layerViews);
+        Animator anim = mWorkspace.setStateWithAnimation(toState, animated, layerViews);
         updateInteraction(fromState, toState);
         return anim;
     }
 
+    public void onLauncherClingShown() {
+        // When a launcher cling appears, it should cover the underlying layers, so their focus
+        // should be blocked.
+        if (mDragLayer.getDescendantFocusability() != ViewGroup.FOCUS_BLOCK_DESCENDANTS) {
+            mDragLayer.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
+        }
+    }
+
+    public void onLauncherClingDismissed() {
+        mDragLayer.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
+    }
+
     public void enterSpringLoadedDragMode() {
         if (LOGD) Log.d(TAG, String.format("enterSpringLoadedDragMode [mState=%s", mState.name()));
         if (mState == State.WORKSPACE || mState == State.APPS_SPRING_LOADED ||
@@ -3414,8 +3338,7 @@
         }
 
         mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
-                Workspace.State.SPRING_LOADED,
-                WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, true /* animated */,
+                Workspace.State.SPRING_LOADED, true /* animated */,
                 null /* onCompleteRunnable */);
         mState = isAppsViewVisible() ? State.APPS_SPRING_LOADED : State.WIDGETS_SPRING_LOADED;
     }
@@ -3636,7 +3559,7 @@
         if (mWorkspace != null) {
             return mWorkspace.getCurrentPage();
         } else {
-            return SCREEN_COUNT / 2;
+            return 0;
         }
     }
 
@@ -3797,7 +3720,7 @@
                             Object tag = v.getTag();
                             String desc = "Collision while binding workspace item: " + item
                                     + ". Collides with " + tag;
-                            if (LauncherAppState.isDogfoodBuild()) {
+                            if (ProviderConfig.IS_DOGFOOD_BUILD) {
                                 throw (new RuntimeException(desc));
                             } else {
                                 Log.d(TAG, desc);
@@ -4093,10 +4016,7 @@
     }
 
     private ValueAnimator createNewAppBounceAnimation(View v, int i) {
-        ValueAnimator bounceAnim = LauncherAnimUtils.ofPropertyValuesHolder(v,
-                PropertyValuesHolder.ofFloat("alpha", 1f),
-                PropertyValuesHolder.ofFloat("scaleX", 1f),
-                PropertyValuesHolder.ofFloat("scaleY", 1f));
+        ValueAnimator bounceAnim = LauncherAnimUtils.ofViewAlphaAndScale(v, 1, 1, 1);
         bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
         bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY);
         bounceAnim.setInterpolator(new OvershootInterpolator(BOUNCE_ANIMATION_TENSION));
@@ -4355,7 +4275,7 @@
                     public void run() {
                         setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
                     }
-                }, mRestoreScreenOrientationDelay);
+                }, RESTORE_SCREEN_ORIENTATION_DELAY);
             }
         }
     }
@@ -4622,7 +4542,6 @@
         Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading);
         Log.d(TAG, "mRestoring=" + mRestoring);
         Log.d(TAG, "mWaitingForResult=" + mWaitingForResult);
-        Log.d(TAG, "mSavedInstanceState=" + mSavedInstanceState);
         Log.d(TAG, "sFolders.size=" + sFolders.size());
         mModel.dumpState();
         // TODO(hyunyoungs): add mWidgetsView.dumpState(); or mWidgetsModel.dumpState();
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index 6a248a3..853c2ec 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -21,14 +21,10 @@
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
-import android.annotation.TargetApi;
-import android.os.Build;
+import android.util.Property;
 import android.view.View;
-import android.view.ViewAnimationUtils;
 import android.view.ViewTreeObserver;
 
-import com.android.launcher3.util.UiThreadCircularReveal;
-
 import java.util.HashSet;
 import java.util.WeakHashMap;
 
@@ -102,42 +98,32 @@
         return anim;
     }
 
-    public static ObjectAnimator ofFloat(View target, String propertyName, float... values) {
-        ObjectAnimator anim = new ObjectAnimator();
-        anim.setTarget(target);
-        anim.setPropertyName(propertyName);
-        anim.setFloatValues(values);
+    public static ObjectAnimator ofFloat(View target, Property<View, Float> property,
+            float... values) {
+        ObjectAnimator anim = ObjectAnimator.ofFloat(target, property, values);
         cancelOnDestroyActivity(anim);
         new FirstFrameAnimatorHelper(anim, target);
         return anim;
     }
 
+    public static ObjectAnimator ofViewAlphaAndScale(View target,
+            float alpha, float scaleX, float scaleY) {
+        return ofPropertyValuesHolder(target,
+                PropertyValuesHolder.ofFloat(View.ALPHA, alpha),
+                PropertyValuesHolder.ofFloat(View.SCALE_X, scaleX),
+                PropertyValuesHolder.ofFloat(View.SCALE_Y, scaleY));
+    }
+
     public static ObjectAnimator ofPropertyValuesHolder(View target,
             PropertyValuesHolder... values) {
-        ObjectAnimator anim = new ObjectAnimator();
-        anim.setTarget(target);
-        anim.setValues(values);
-        cancelOnDestroyActivity(anim);
-        new FirstFrameAnimatorHelper(anim, target);
-        return anim;
+        return ofPropertyValuesHolder(target, target, values);
     }
 
     public static ObjectAnimator ofPropertyValuesHolder(Object target,
             View view, PropertyValuesHolder... values) {
-        ObjectAnimator anim = new ObjectAnimator();
-        anim.setTarget(target);
-        anim.setValues(values);
+        ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(target, values);
         cancelOnDestroyActivity(anim);
         new FirstFrameAnimatorHelper(anim, view);
         return anim;
     }
-
-    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
-    public static ValueAnimator createCircularReveal(View view, int centerX,
-            int centerY, float startRadius, float endRadius) {
-        ValueAnimator anim = UiThreadCircularReveal.createCircularReveal(view, centerX,
-                centerY, startRadius, endRadius);
-        new FirstFrameAnimatorHelper(anim, view);
-        return anim;
-    }
 }
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index d87ad67..5a7fadb 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -17,10 +17,10 @@
 package com.android.launcher3;
 
 import android.app.SearchManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.os.UserManager;
 import android.util.Log;
 
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
@@ -34,12 +34,11 @@
 public class LauncherAppState {
 
     private final AppFilter mAppFilter;
-    private final BuildInfo mBuildInfo;
     @Thunk final LauncherModel mModel;
     private final IconCache mIconCache;
     private final WidgetPreviewLoader mWidgetCache;
 
-    private boolean mWallpaperChangedSinceLastCheck;
+    @Thunk boolean mWallpaperChangedSinceLastCheck;
 
     private static WeakReference<LauncherProvider> sLauncherProvider;
     private static Context sContext;
@@ -88,7 +87,6 @@
         mWidgetCache = new WidgetPreviewLoader(sContext, mIconCache);
 
         mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class));
-        mBuildInfo = BuildInfo.loadByName(sContext.getString(R.string.build_info_class));
         mModel = new LauncherModel(this, mIconCache, mAppFilter);
 
         LauncherAppsCompat.getInstance(sContext).addOnAppsChangedCallback(mModel);
@@ -103,6 +101,16 @@
 
         sContext.registerReceiver(mModel, filter);
         UserManagerCompat.getInstance(sContext).enableAndResetCache();
+
+        if (!Utilities.ATLEAST_KITKAT) {
+            sContext.registerReceiver(new BroadcastReceiver() {
+
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    mWallpaperChangedSinceLastCheck = true;
+                }
+            }, new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED));
+        }
     }
 
     /**
@@ -159,10 +167,6 @@
     public WidgetPreviewLoader getWidgetCache() {
         return mWidgetCache;
     }
-    
-    public void onWallpaperChanged() {
-        mWallpaperChangedSinceLastCheck = true;
-    }
 
     public boolean hasWallpaperChangedSinceLastCheck() {
         boolean result = mWallpaperChangedSinceLastCheck;
@@ -173,8 +177,4 @@
     public InvariantDeviceProfile getInvariantDeviceProfile() {
         return mInvariantDeviceProfile;
     }
-
-    public static boolean isDogfoodBuild() {
-        return getInstance().mBuildInfo.isDogfoodBuild();
-    }
 }
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 18fe8ef..43d05a6 100644
--- a/src/com/android/launcher3/LauncherClings.java
+++ b/src/com/android/launcher3/LauncherClings.java
@@ -27,9 +27,11 @@
 import android.os.Bundle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.View.OnClickListener;
+import android.view.View.OnKeyListener;
 import android.view.View.OnLongClickListener;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
@@ -37,7 +39,7 @@
 
 import com.android.launcher3.util.Thunk;
 
-class LauncherClings implements OnClickListener {
+class LauncherClings implements OnClickListener, OnKeyListener {
     private static final String MIGRATION_CLING_DISMISSED_KEY = "cling_gel.migration.dismissed";
     private static final String WORKSPACE_CLING_DISMISSED_KEY = "cling_gel.workspace.dismissed";
 
@@ -85,6 +87,20 @@
         }
     }
 
+    @Override
+    public boolean onKey(View v, int keyCode, KeyEvent event) {
+        if (event.isPrintingKey()) {
+            // Should ignore all printing keys, otherwise they come to the search box.
+            return true;
+        }
+        if (keyCode == KeyEvent.KEYCODE_MENU) {
+            // Menu key goes to the overview mode similar to longpress, therefore it needs to
+            // dismiss the clings.
+            dismissLongPressCling();
+        }
+        return false;
+    }
+
     /**
      * Shows the migration cling.
      *
@@ -92,6 +108,7 @@
      * package was not preinstalled and there exists a db to migrate from.
      */
     public void showMigrationCling() {
+        mLauncher.onLauncherClingShown();
         mIsVisible = true;
         mLauncher.hideWorkspaceSearchAndHotseat();
 
@@ -136,7 +153,9 @@
         final ViewGroup content = (ViewGroup) cling.findViewById(R.id.cling_content);
         mInflater.inflate(showWelcome ? R.layout.longpress_cling_welcome_content
                 : R.layout.longpress_cling_content, content);
-        content.findViewById(R.id.cling_dismiss_longpress_info).setOnClickListener(this);
+        final View button = content.findViewById(R.id.cling_dismiss_longpress_info);
+        button.setOnClickListener(this);
+        button.setOnKeyListener(this);
 
         if (TAG_CROP_TOP_AND_SIDES.equals(content.getTag())) {
             Drawable bg = new BorderCropDrawable(mLauncher.getResources().getDrawable(R.drawable.cling_bg),
@@ -144,6 +163,7 @@
             content.setBackground(bg);
         }
 
+        mLauncher.onLauncherClingShown();
         root.addView(cling);
 
         if (showWelcome) {
@@ -161,12 +181,12 @@
                 ObjectAnimator anim;
                 if (TAG_CROP_TOP_AND_SIDES.equals(content.getTag())) {
                     content.setTranslationY(-content.getMeasuredHeight());
-                    anim = LauncherAnimUtils.ofFloat(content, "translationY", 0);
+                    anim = LauncherAnimUtils.ofFloat(content, View.TRANSLATION_Y, 0);
                 } else {
                     content.setScaleX(0);
                     content.setScaleY(0);
-                    PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1);
-                    PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1);
+                    PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat(View.SCALE_X, 1);
+                    PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat(View.SCALE_Y, 1);
                     anim = LauncherAnimUtils.ofPropertyValuesHolder(content, scaleX, scaleY);
                 }
 
@@ -180,7 +200,12 @@
     @Thunk void dismissLongPressCling() {
         Runnable dismissCb = new Runnable() {
             public void run() {
-                dismissCling(mLauncher.findViewById(R.id.longpress_cling), null,
+                Runnable cb = new Runnable() {
+                    public void run() {
+                        mLauncher.onLauncherClingDismissed();
+                    }
+                };
+                dismissCling(mLauncher.findViewById(R.id.longpress_cling), cb,
                         WORKSPACE_CLING_DISMISSED_KEY, DISMISS_CLING_DURATION);
             }
         };
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index e5ca778..636af7d 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -55,6 +55,7 @@
 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.config.ProviderConfig;
 import com.android.launcher3.model.MigrateFromRestoreTask;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.util.ComponentKey;
@@ -648,12 +649,7 @@
                         modelShortcut.cellX == shortcut.cellX &&
                         modelShortcut.cellY == shortcut.cellY &&
                         modelShortcut.spanX == shortcut.spanX &&
-                        modelShortcut.spanY == shortcut.spanY &&
-                        ((modelShortcut.dropPos == null && shortcut.dropPos == null) ||
-                        (modelShortcut.dropPos != null &&
-                                shortcut.dropPos != null &&
-                                modelShortcut.dropPos[0] == shortcut.dropPos[0] &&
-                        modelShortcut.dropPos[1] == shortcut.dropPos[1]))) {
+                        modelShortcut.spanY == shortcut.spanY) {
                     // For all intents and purposes, this is the same object
                     return;
                 }
@@ -880,7 +876,7 @@
     }
 
     private void assertWorkspaceLoaded() {
-        if (LauncherAppState.isDogfoodBuild()) {
+        if (ProviderConfig.IS_DOGFOOD_BUILD) {
             synchronized (mLock) {
                 if (!mHasLoaderCompletedOnce ||
                         (mLoaderTask != null && mLoaderTask.mIsLoadingAndBindingWorkspace)) {
@@ -1611,11 +1607,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) {
@@ -1657,7 +1654,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;
             }
@@ -1719,13 +1721,12 @@
             final PackageManager manager = context.getPackageManager();
             final boolean isSafeMode = manager.isSafeMode();
             final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
-            final boolean isSdCardReady = context.registerReceiver(null,
-                    new IntentFilter(StartupReceiver.SYSTEM_READY)) != null;
+            final boolean isSdCardReady = Utilities.isBootCompleted();
 
             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();
@@ -1765,6 +1766,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>();
@@ -1966,6 +1968,7 @@
                                 } catch (URISyntaxException e) {
                                     Launcher.addDumpLog(TAG,
                                             "Invalid uri: " + intentDescription, true);
+                                    itemsToRemove.add(id);
                                     continue;
                                 }
 
@@ -2036,7 +2039,7 @@
                                     }
 
                                     // check & update map of what's occupied
-                                    if (!checkItemPlacement(occupied, info)) {
+                                    if (!checkItemPlacement(occupied, info, sBgWorkspaceScreens)) {
                                         itemsToRemove.add(id);
                                         break;
                                     }
@@ -2087,7 +2090,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;
                                 }
@@ -2207,13 +2210,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;
                                     }
@@ -2298,12 +2302,10 @@
 
                 if (!isSdCardReady && !sPendingPackages.isEmpty()) {
                     context.registerReceiver(new AppsAvailabilityCheck(),
-                            new IntentFilter(StartupReceiver.SYSTEM_READY),
+                            new IntentFilter(Intent.ACTION_BOOT_COMPLETED),
                             null, sWorker);
                 }
 
-                sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
-
                 // Remove any empty screens
                 ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
                 for (ItemInfo item: sBgItemsIdMap) {
@@ -2450,19 +2452,36 @@
         private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
             final LauncherAppState app = LauncherAppState.getInstance();
             final InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
-            // XXX: review this
+            final int screenCols = profile.numColumns;
+            final int screenCellCount = profile.numColumns * profile.numRows;
             Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
                 @Override
                 public int compare(ItemInfo lhs, ItemInfo rhs) {
-                    int cellCountX = (int) profile.numColumns;
-                    int cellCountY = (int) profile.numRows;
-                    int screenOffset = cellCountX * cellCountY;
-                    int containerOffset = screenOffset * (Launcher.SCREEN_COUNT + 1); // +1 hotseat
-                    long lr = (lhs.container * containerOffset + lhs.screenId * screenOffset +
-                            lhs.cellY * cellCountX + lhs.cellX);
-                    long rr = (rhs.container * containerOffset + rhs.screenId * screenOffset +
-                            rhs.cellY * cellCountX + rhs.cellX);
-                    return (int) (lr - rr);
+                    if (lhs.container == rhs.container) {
+                        // Within containers, order by their spatial position in that container
+                        switch ((int) lhs.container) {
+                            case LauncherSettings.Favorites.CONTAINER_DESKTOP: {
+                                long lr = (lhs.screenId * screenCellCount +
+                                        lhs.cellY * screenCols + lhs.cellX);
+                                long rr = (rhs.screenId * screenCellCount +
+                                        rhs.cellY * screenCols + rhs.cellX);
+                                return (int) (lr - rr);
+                            }
+                            case LauncherSettings.Favorites.CONTAINER_HOTSEAT: {
+                                // We currently use the screen id as the rank
+                                return (int) (lhs.screenId - rhs.screenId);
+                            }
+                            default:
+                                if (ProviderConfig.IS_DOGFOOD_BUILD) {
+                                    throw new RuntimeException("Unexpected container type when " +
+                                            "sorting workspace items.");
+                                }
+                                return 0;
+                        }
+                    } else {
+                        // Between containers, order by hotseat, desktop
+                        return (int) (lhs.container - rhs.container);
+                    }
                 }
             });
         }
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 20844ec..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;
     }
 
@@ -1101,10 +1113,6 @@
                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
                         final int cellYIndex
                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
-                        final int uriIndex
-                                = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
-                        final int displayModeIndex
-                                = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);
                         final int profileIndex
                                 = c.getColumnIndex(LauncherSettings.Favorites.PROFILE_ID);
 
@@ -1223,9 +1231,6 @@
                                     c.getString(iconResourceIndex));
                             values.put(LauncherSettings.Favorites.ITEM_TYPE, itemType);
                             values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1);
-                            values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex));
-                            values.put(LauncherSettings.Favorites.DISPLAY_MODE,
-                                    c.getInt(displayModeIndex));
                             values.put(LauncherSettings.Favorites.PROFILE_ID, userSerialNumber);
 
                             if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
index cdde8c1..0e20fab 100644
--- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java
+++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
@@ -23,15 +23,17 @@
 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;
 import android.view.animation.DecelerateInterpolator;
 
 import com.android.launcher3.allapps.AllAppsContainerView;
-import com.android.launcher3.util.UiThreadCircularReveal;
 import com.android.launcher3.util.Thunk;
+import com.android.launcher3.util.UiThreadCircularReveal;
 import com.android.launcher3.widget.WidgetsContainerView;
 
 import java.util.HashMap;
@@ -162,7 +164,6 @@
             final boolean animated) {
         final WidgetsContainerView toView = mLauncher.getWidgetsView();
         final View buttonView = mLauncher.getWidgetsButton();
-
         PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
             @Override
             public float getMaterialRevealViewFinalAlpha(View revealView) {
@@ -179,7 +180,7 @@
      */
     public void startAnimationToWorkspace(final Launcher.State fromState,
             final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
-            final int toWorkspacePage, final boolean animated, final Runnable onCompleteRunnable) {
+            final boolean animated, final Runnable onCompleteRunnable) {
         if (toWorkspaceState != Workspace.State.NORMAL &&
                 toWorkspaceState != Workspace.State.SPRING_LOADED &&
                 toWorkspaceState != Workspace.State.OVERVIEW) {
@@ -187,10 +188,10 @@
         }
 
         if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED) {
-            startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState, toWorkspacePage,
+            startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState,
                     animated, onCompleteRunnable);
         } else {
-            startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState, toWorkspacePage,
+            startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState,
                     animated, onCompleteRunnable);
         }
     }
@@ -222,7 +223,7 @@
 
         // Create the workspace animation.
         // NOTE: this call apparently also sets the state for the workspace if !animated
-        Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState, -1,
+        Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState,
                 animated, layerViews);
 
         // Animate the search bar
@@ -257,11 +258,11 @@
 
             // Create the animators
             PropertyValuesHolder panelAlpha =
-                    PropertyValuesHolder.ofFloat("alpha", revealViewToAlpha, 1f);
+                    PropertyValuesHolder.ofFloat(View.ALPHA, revealViewToAlpha, 1f);
             PropertyValuesHolder panelDriftY =
-                    PropertyValuesHolder.ofFloat("translationY", revealViewToYDrift, 0);
+                    PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, revealViewToYDrift, 0);
             PropertyValuesHolder panelDriftX =
-                    PropertyValuesHolder.ofFloat("translationX", revealViewToXDrift, 0);
+                    PropertyValuesHolder.ofFloat(View.TRANSLATION_X, revealViewToXDrift, 0);
             ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView,
                     panelAlpha, panelDriftY, panelDriftX);
             panelAlphaAndDrift.setDuration(revealDuration);
@@ -362,7 +363,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();
                         }
                     }
@@ -405,8 +406,8 @@
      * Starts and animation to the workspace from the apps view.
      */
     private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState,
-            final Workspace.State toWorkspaceState, final int toWorkspacePage,
-            final boolean animated, final Runnable onCompleteRunnable) {
+            final Workspace.State toWorkspaceState, final boolean animated, 
+            final Runnable onCompleteRunnable) {
         AllAppsContainerView appsView = mLauncher.getAppsView();
         PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
             @Override
@@ -441,7 +442,7 @@
         };
         // Only animate the search bar if animating to spring loaded mode from all apps
         mCurrentAnimation = startAnimationToWorkspaceFromOverlay(fromWorkspaceState, toWorkspaceState,
-                toWorkspacePage, mLauncher.getAllAppsButton(), appsView, appsView.getContentView(),
+                mLauncher.getAllAppsButton(), appsView, appsView.getContentView(),
                 appsView.getRevealView(), appsView.getSearchBarView(), animated,
                 onCompleteRunnable, cb);
     }
@@ -450,8 +451,8 @@
      * Starts and animation to the workspace from the widgets view.
      */
     private void startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState,
-            final Workspace.State toWorkspaceState, final int toWorkspacePage,
-            final boolean animated, final Runnable onCompleteRunnable) {
+            final Workspace.State toWorkspaceState, final boolean animated, 
+            final Runnable onCompleteRunnable) {
         final WidgetsContainerView widgetsView = mLauncher.getWidgetsView();
         PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
             @Override
@@ -470,7 +471,7 @@
             }
         };
         mCurrentAnimation = startAnimationToWorkspaceFromOverlay(fromWorkspaceState,
-                toWorkspaceState, toWorkspacePage, mLauncher.getWidgetsButton(), widgetsView,
+                toWorkspaceState, mLauncher.getWidgetsButton(), widgetsView,
                 widgetsView.getContentView(), widgetsView.getRevealView(), null, animated,
                 onCompleteRunnable, cb);
     }
@@ -479,7 +480,7 @@
      * Creates and starts a new animation to the workspace.
      */
     private AnimatorSet startAnimationToWorkspaceFromOverlay(final Workspace.State fromWorkspaceState,
-            final Workspace.State toWorkspaceState, final int toWorkspacePage, final View buttonView,
+            final Workspace.State toWorkspaceState, final View buttonView,
             final View fromView, final View contentView, final View revealView,
             final View overlaySearchBarView, final boolean animated, final Runnable onCompleteRunnable,
             final PrivateTransitionCallbacks pCb) {
@@ -503,7 +504,7 @@
         // Create the workspace animation.
         // NOTE: this call apparently also sets the state for the workspace if !animated
         Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState,
-                toWorkspacePage, animated, layerViews);
+                animated, layerViews);
 
         // Animate the search bar
         startWorkspaceSearchBarAnimation(animation, fromWorkspaceState, toWorkspaceState,
@@ -657,6 +658,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
@@ -671,7 +673,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();
                         }
                     }
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 05f0a05..5f54e1d 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -18,10 +18,10 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
 import android.animation.LayoutTransition;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
+import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.TypedArray;
@@ -47,10 +47,8 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.Interpolator;
-
 import com.android.launcher3.util.LauncherEdgeEffect;
 import com.android.launcher3.util.Thunk;
-
 import java.util.ArrayList;
 
 /**
@@ -65,9 +63,8 @@
     // 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;
-    protected static final float NANOTIME_DIV = 1000000000.0f;
 
     private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f;
     // The page is moved more than halfway, automatically move to the next page on touch up.
@@ -87,25 +84,19 @@
     private int mFreeScrollMinScrollX = -1;
     private int mFreeScrollMaxScrollX = -1;
 
-    static final int AUTOMATIC_PAGE_SPACING = -1;
-
     protected int mFlingThresholdVelocity;
     protected int mMinFlingVelocity;
     protected int mMinSnapVelocity;
 
-    protected float mDensity;
-    protected float mSmoothingTime;
-    protected float mTouchX;
-
     protected boolean mFirstLayout = true;
     private int mNormalChildHeight;
 
     protected int mCurrentPage;
     protected int mRestorePage = INVALID_RESTORE_PAGE;
-    protected int mChildCountOnLastLayout;
+    private int mChildCountOnLastLayout;
 
     protected int mNextPage = INVALID_PAGE;
-    protected int mMaxScrollX;
+    private int mMaxScrollX;
     protected LauncherScroller mScroller;
     private Interpolator mDefaultInterpolator;
     private VelocityTracker mVelocityTracker;
@@ -117,10 +108,10 @@
     private float mDownMotionY;
     private float mDownScrollX;
     private float mDragViewBaselineLeft;
-    protected float mLastMotionX;
-    protected float mLastMotionXRemainder;
-    protected float mLastMotionY;
-    protected float mTotalMotionX;
+    private float mLastMotionX;
+    private float mLastMotionXRemainder;
+    private float mLastMotionY;
+    private float mTotalMotionX;
     private int mLastScreenCenter = -1;
 
     private boolean mCancelTap;
@@ -133,23 +124,17 @@
     protected final static int TOUCH_STATE_NEXT_PAGE = 3;
     protected final static int TOUCH_STATE_REORDERING = 4;
 
-    protected final static float ALPHA_QUANTIZE_LEVEL = 0.0001f;
-
     protected int mTouchState = TOUCH_STATE_REST;
-    protected boolean mForceScreenScrolled = false;
+    private boolean mForceScreenScrolled = false;
 
     protected OnLongClickListener mLongClickListener;
 
     protected int mTouchSlop;
     private int mMaximumVelocity;
-    protected int mPageLayoutWidthGap;
-    protected int mPageLayoutHeightGap;
     protected int mCellCountX = 0;
     protected int mCellCountY = 0;
-    protected boolean mCenterPagesVertically;
     protected boolean mAllowOverScroll = true;
     protected int[] mTempVisiblePagesRange = new int[2];
-    protected boolean mForceDrawAllChildrenNextFrame;
 
     protected static final int INVALID_POINTER = -1;
 
@@ -180,7 +165,7 @@
 
     private float mMinScale = 1f;
     private boolean mUseMinScale = false;
-    protected View mDragView;
+    @Thunk View mDragView;
     private Runnable mSidePageHoverRunnable;
     @Thunk int mSidePageHoverIndex = -1;
     // This variable's scope is only for the duration of startReordering() and endReordering()
@@ -189,7 +174,7 @@
     // animation after endReordering()
     private boolean mIsReordering;
     // The runnable that settles the page after snapToPage and animateDragViewToOriginalPosition
-    private int NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT = 2;
+    private static final int NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT = 2;
     private int mPostReorderingPreZoomInRemainingAnimationCount;
     private Runnable mPostReorderingPreZoomInRunnable;
 
@@ -223,11 +208,6 @@
 
         TypedArray a = context.obtainStyledAttributes(attrs,
                 R.styleable.PagedView, defStyle, 0);
-
-        mPageLayoutWidthGap = a.getDimensionPixelSize(
-                R.styleable.PagedView_pageLayoutWidthGap, 0);
-        mPageLayoutHeightGap = a.getDimensionPixelSize(
-                R.styleable.PagedView_pageLayoutHeightGap, 0);
         mPageIndicatorViewId = a.getResourceId(R.styleable.PagedView_pageIndicator, -1);
         a.recycle();
 
@@ -243,16 +223,15 @@
         mScroller = new LauncherScroller(getContext());
         setDefaultInterpolator(new ScrollInterpolator());
         mCurrentPage = 0;
-        mCenterPagesVertically = true;
 
         final ViewConfiguration configuration = ViewConfiguration.get(getContext());
         mTouchSlop = configuration.getScaledPagingTouchSlop();
         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
-        mDensity = getResources().getDisplayMetrics().density;
 
-        mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity);
-        mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * mDensity);
-        mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * mDensity);
+        float density = getResources().getDisplayMetrics().density;
+        mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * density);
+        mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * density);
+        mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * density);
         setOnHierarchyChangeListener(this);
         setWillNotDraw(false);
     }
@@ -393,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;
@@ -402,7 +384,7 @@
     /**
      * Returns the index of page to be shown immediately afterwards.
      */
-    int getNextPage() {
+    public int getNextPage() {
         return (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
     }
 
@@ -606,9 +588,6 @@
             super.scrollTo(x, y);
         }
 
-        mTouchX = x;
-        mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
-
         // Update the last motion events when scrolling
         if (isReordering(true)) {
             float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY);
@@ -836,6 +815,7 @@
         setMeasuredDimension(scaledWidthSize, scaledHeightSize);
     }
 
+    @SuppressLint("DrawAllocation")
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         if (getChildCount() == 0) {
@@ -874,9 +854,7 @@
                     childTop = offsetY;
                 } else {
                     childTop = offsetY + getPaddingTop() + mInsets.top;
-                    if (mCenterPagesVertically) {
-                        childTop += (getViewportHeight() - mInsets.top - mInsets.bottom - verticalPadding - child.getMeasuredHeight()) / 2;
-                    }
+                    childTop += (getViewportHeight() - mInsets.top - mInsets.bottom - verticalPadding - child.getMeasuredHeight()) / 2;
                 }
 
                 final int childWidth = child.getMeasuredWidth();
@@ -1064,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);
@@ -1089,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;
@@ -1136,8 +1113,7 @@
                 for (int i = pageCount - 1; i >= 0; i--) {
                     final View v = getPageAt(i);
                     if (v == mDragView) continue;
-                    if (mForceDrawAllChildrenNextFrame ||
-                               (leftScreen <= i && i <= rightScreen && shouldDrawChild(v))) {
+                    if (leftScreen <= i && i <= rightScreen && shouldDrawChild(v)) {
                         drawChild(canvas, v, drawingTime);
                     }
                 }
@@ -1146,7 +1122,6 @@
                     drawChild(canvas, mDragView, drawingTime);
                 }
 
-                mForceDrawAllChildrenNextFrame = false;
                 canvas.restore();
             }
         }
@@ -1450,8 +1425,6 @@
             mTotalMotionX += Math.abs(mLastMotionX - x);
             mLastMotionX = x;
             mLastMotionXRemainder = 0;
-            mTouchX = getViewportOffsetX() + getScrollX();
-            mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
             onScrollInteractionBegin();
             pageBeginMoving();
         }
@@ -1650,8 +1623,6 @@
                 // keep the remainder because we are actually testing if we've moved from the last
                 // scrolled position (which is discrete).
                 if (Math.abs(deltaX) >= 1.0f) {
-                    mTouchX += deltaX;
-                    mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
                     scrollBy((int) deltaX, 0);
                     mLastMotionX = x;
                     mLastMotionXRemainder = deltaX - (int) deltaX;
@@ -1711,16 +1682,14 @@
 
                                     // Animate the view translation from its old position to its new
                                     // position
-                                    AnimatorSet anim = (AnimatorSet) v.getTag(ANIM_TAG_KEY);
+                                    ObjectAnimator anim = (ObjectAnimator) v.getTag();
                                     if (anim != null) {
                                         anim.cancel();
                                     }
 
                                     v.setTranslationX(oldX - newX);
-                                    anim = new AnimatorSet();
+                                    anim = LauncherAnimUtils.ofFloat(v, View.TRANSLATION_X, 0);
                                     anim.setDuration(REORDERING_REORDER_REPOSITION_DURATION);
-                                    anim.playTogether(
-                                            ObjectAnimator.ofFloat(v, "translationX", 0f));
                                     anim.start();
                                     v.setTag(anim);
                                 }
@@ -2170,13 +2139,12 @@
     // Animate the drag view back to the original position
     private void animateDragViewToOriginalPosition() {
         if (mDragView != null) {
-            AnimatorSet anim = new AnimatorSet();
-            anim.setDuration(REORDERING_DROP_REPOSITION_DURATION);
-            anim.playTogether(
-                    ObjectAnimator.ofFloat(mDragView, "translationX", 0f),
-                    ObjectAnimator.ofFloat(mDragView, "translationY", 0f),
-                    ObjectAnimator.ofFloat(mDragView, "scaleX", 1f),
-                    ObjectAnimator.ofFloat(mDragView, "scaleY", 1f));
+            Animator anim = new LauncherViewPropertyAnimator(mDragView)
+                    .translationX(0)
+                    .translationY(0)
+                    .scaleX(1)
+                    .scaleY(1)
+                    .setDuration(REORDERING_DROP_REPOSITION_DURATION);
             anim.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
@@ -2273,9 +2241,8 @@
         animateDragViewToOriginalPosition();
     }
 
-    private static final int ANIM_TAG_KEY = 100;
-
     /* Accessibility */
+    @SuppressWarnings("deprecation")
     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
     @Override
     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
@@ -2335,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 772a334..fdcad82 100644
--- a/src/com/android/launcher3/SearchDropTargetBar.java
+++ b/src/com/android/launcher3/SearchDropTargetBar.java
@@ -26,6 +26,7 @@
 import android.view.animation.AccelerateInterpolator;
 import android.widget.FrameLayout;
 
+import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.util.Thunk;
 
 /*
@@ -205,7 +206,7 @@
      * DragController.DragListener implementation
      */
     @Override
-    public void onDragStart(DragSource source, Object info, int dragAction) {
+    public void onDragStart(DragSource source, ItemInfo info, int dragAction) {
         animateToState(State.DROP_TARGET, DEFAULT_DRAG_FADE_DURATION);
     }
 
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index 157b48a..20c2773 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -18,12 +18,9 @@
 
 import android.app.WallpaperManager;
 import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Paint;
 import android.graphics.Rect;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
 
 public class ShortcutAndWidgetContainer extends ViewGroup {
     static final String TAG = "CellLayoutChildren";
@@ -43,7 +40,6 @@
     private int mHeightGap;
 
     private int mCountX;
-    private int mCountY;
 
     private Launcher mLauncher;
 
@@ -62,7 +58,6 @@
         mWidthGap = widthGap;
         mHeightGap = heightGap;
         mCountX = countX;
-        mCountY = countY;
     }
 
     public View getChildAt(int x, int y) {
@@ -80,24 +75,6 @@
     }
 
     @Override
-    protected void dispatchDraw(Canvas canvas) {
-        @SuppressWarnings("all") // suppress dead code warning
-        final boolean debug = false;
-        if (debug) {
-            // Debug drawing for hit space
-            Paint p = new Paint();
-            p.setColor(0x6600FF00);
-            for (int i = getChildCount() - 1; i >= 0; i--) {
-                final View child = getChildAt(i);
-                final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
-
-                canvas.drawRect(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height, p);
-            }
-        }
-        super.dispatchDraw(canvas);
-    }
-
-    @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         int count = getChildCount();
 
@@ -238,7 +215,6 @@
         }
     }
 
-    @Override
     protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
         super.setChildrenDrawnWithCacheEnabled(enabled);
     }
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index 5766cf2..a86a2f9 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -29,7 +29,6 @@
 import com.android.launcher3.compat.UserManagerCompat;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 
 /**
  * Represents a launchable icon on the workspaces and in folders.
@@ -71,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)
@@ -245,7 +244,7 @@
         return "ShortcutInfo(title=" + title + "intent=" + intent + "id=" + this.id
                 + " type=" + this.itemType + " container=" + this.container + " screen=" + screenId
                 + " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX + " spanY=" + spanY
-                + " dropPos=" + Arrays.toString(dropPos) + " user=" + user + ")";
+                + " user=" + user + ")";
     }
 
     public static void dumpShortcutInfoList(String tag, String label,
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/StartupReceiver.java b/src/com/android/launcher3/StartupReceiver.java
deleted file mode 100644
index 65f913f..0000000
--- a/src/com/android/launcher3/StartupReceiver.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.android.launcher3;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-public class StartupReceiver extends BroadcastReceiver {
-
-    static final String SYSTEM_READY = "com.android.launcher3.SYSTEM_READY";
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        context.sendStickyBroadcast(new Intent(SYSTEM_READY));
-    }
-}
diff --git a/src/com/android/launcher3/Stats.java b/src/com/android/launcher3/Stats.java
index cb0e252..4aba150 100644
--- a/src/com/android/launcher3/Stats.java
+++ b/src/com/android/launcher3/Stats.java
@@ -25,6 +25,8 @@
 import android.view.View;
 import android.view.ViewParent;
 
+import com.android.launcher3.config.ProviderConfig;
+
 public class Stats {
 
     /**
@@ -71,7 +73,7 @@
 
             if (provider != null) {
                 provider.fillInLaunchSourceData(sourceData);
-            } else if (LauncherAppState.isDogfoodBuild()) {
+            } else if (ProviderConfig.IS_DOGFOOD_BUILD) {
                 throw new RuntimeException("Expected LaunchSourceProvider");
             }
         }
diff --git a/src/com/android/launcher3/StylusEventHelper.java b/src/com/android/launcher3/StylusEventHelper.java
index e03273a..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;
diff --git a/src/com/android/launcher3/UninstallDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java
index 955d401..9ed4fb6 100644
--- a/src/com/android/launcher3/UninstallDropTarget.java
+++ b/src/com/android/launcher3/UninstallDropTarget.java
@@ -8,7 +8,7 @@
 import android.os.UserManager;
 import android.util.AttributeSet;
 import android.util.Pair;
-import com.android.launcher3.R;
+
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.util.Thunk;
 
@@ -32,7 +32,7 @@
     }
 
     @Override
-    protected boolean supportsDrop(DragSource source, Object info) {
+    protected boolean supportsDrop(DragSource source, ItemInfo info) {
         return supportsDrop(getContext(), info);
     }
 
@@ -81,7 +81,7 @@
     @Override
     void completeDrop(final DragObject d) {
         final Pair<ComponentName, Integer> componentInfo = getAppInfoFlags(d.dragInfo);
-        final UserHandleCompat user = ((ItemInfo) d.dragInfo).user;
+        final UserHandleCompat user = d.dragInfo.user;
         if (startUninstallActivity(mLauncher, d.dragInfo)) {
 
             final Runnable checkIfUninstallWasSuccess = new Runnable() {
@@ -99,9 +99,9 @@
         }
     }
 
-    public static boolean startUninstallActivity(Launcher launcher, Object info) {
+    public static boolean startUninstallActivity(Launcher launcher, ItemInfo info) {
         final Pair<ComponentName, Integer> componentInfo = getAppInfoFlags(info);
-        final UserHandleCompat user = ((ItemInfo) info).user;
+        final UserHandleCompat user = info.user;
         return launcher.startApplicationUninstallActivity(
                 componentInfo.first, componentInfo.second, user);
     }
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index adedd33..4e93684 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -56,8 +56,11 @@
 import android.view.View;
 import android.widget.Toast;
 
+import com.android.launcher3.config.ProviderConfig;
+
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Locale;
 import java.util.Set;
@@ -116,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;
     }
@@ -128,6 +130,18 @@
         return sForceEnableRotation || context.getResources().getBoolean(R.bool.allow_rotation);
     }
 
+    public static boolean isNycOrAbove() {
+        // TODO(vadimt): Replace using reflection with looking at the API version once
+        // Build.VERSION.SDK_INT gets bumped to 24. b/22942492.
+        try {
+            View.class.getDeclaredField("DRAG_FLAG_OPAQUE");
+            // View.DRAG_FLAG_OPAQUE doesn't exist in M-release, so it's an indication of N+.
+            return true;
+        } catch (NoSuchFieldException e) {
+            return false;
+        }
+    }
+
     public static Bitmap createIconBitmap(Cursor c, int iconIndex, Context context) {
         byte[] data = c.getBlob(iconIndex);
         try {
@@ -340,15 +354,6 @@
                 localY < (v.getHeight() + slop);
     }
 
-    public static void scaleRect(Rect r, float scale) {
-        if (scale != 1.0f) {
-            r.left = (int) (r.left * scale + 0.5f);
-            r.top = (int) (r.top * scale + 0.5f);
-            r.right = (int) (r.right * scale + 0.5f);
-            r.bottom = (int) (r.bottom * scale + 0.5f);
-        }
-    }
-
     public static int[] getCenterDeltaInScreenSpace(View v0, View v1, int[] delta) {
         v0.getLocationInWindow(sLoc0);
         v1.getLocationInWindow(sLoc1);
@@ -369,11 +374,18 @@
     }
 
     public static void scaleRectAboutCenter(Rect r, float scale) {
-        int cx = r.centerX();
-        int cy = r.centerY();
-        r.offset(-cx, -cy);
-        Utilities.scaleRect(r, scale);
-        r.offset(cx, cy);
+        if (scale != 1.0f) {
+            int cx = r.centerX();
+            int cy = r.centerY();
+            r.offset(-cx, -cy);
+
+            r.left = (int) (r.left * scale + 0.5f);
+            r.top = (int) (r.top * scale + 0.5f);
+            r.right = (int) (r.right * scale + 0.5f);
+            r.bottom = (int) (r.bottom * scale + 0.5f);
+
+            r.offset(cx, cy);
+        }
     }
 
     public static void startActivityForResultSafely(
@@ -516,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.
@@ -661,7 +663,7 @@
     }
 
     public static void assertWorkerThread() {
-        if (LauncherAppState.isDogfoodBuild() &&
+        if (ProviderConfig.IS_DOGFOOD_BUILD &&
                 (LauncherModel.sWorkerThread.getThreadId() != Process.myTid())) {
             throw new IllegalStateException();
         }
@@ -710,4 +712,27 @@
     public static String createDbSelectionQuery(String columnName, Iterable<?> values) {
         return String.format(Locale.ENGLISH, "%s IN (%s)", columnName, TextUtils.join(", ", values));
     }
+
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    public static boolean isBootCompleted() {
+        try {
+            Class clazz = Class.forName("android.os.SystemProperties");
+            Method getter = clazz.getDeclaredMethod("get", String.class);
+            String value = (String) getter.invoke(null, "sys.boot_completed");
+            return "1".equals(value);
+        } catch (Exception e) {
+            Log.d(TAG, "Unable to read system properties");
+            // Assume that boot has completed
+            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/WallpaperChangedReceiver.java b/src/com/android/launcher3/WallpaperChangedReceiver.java
deleted file mode 100644
index 2d5612f..0000000
--- a/src/com/android/launcher3/WallpaperChangedReceiver.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-public class WallpaperChangedReceiver extends BroadcastReceiver {
-    public void onReceive(Context context, Intent data) {
-        LauncherAppState.setApplicationContext(context.getApplicationContext());
-        LauncherAppState appState = LauncherAppState.getInstance();
-        appState.onWallpaperChanged();
-    }
-}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 44d70ba..5c8c402 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -28,12 +28,10 @@
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.SharedPreferences;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
-import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Point;
 import android.graphics.PointF;
@@ -50,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;
@@ -68,6 +65,12 @@
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.AccessibilityDragSource;
 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;
@@ -92,13 +95,17 @@
 
     private static boolean ENFORCE_DRAG_EVENT_ORDER = false;
 
-    protected static final int SNAP_OFF_EMPTY_SCREEN_DURATION = 400;
-    protected static final int FADE_EMPTY_SCREEN_DURATION = 150;
+    private static final int SNAP_OFF_EMPTY_SCREEN_DURATION = 400;
+    private static final int FADE_EMPTY_SCREEN_DURATION = 150;
 
     private static final int ADJACENT_SCREEN_DROP_DURATION = 300;
 
-    static final boolean MAP_NO_RECURSE = false;
-    static final boolean MAP_RECURSE = true;
+    private static final boolean MAP_NO_RECURSE = false;
+    private static final boolean MAP_RECURSE = true;
+
+    // The screen id used for the empty screen always present to the right.
+    public final static long EXTRA_EMPTY_SCREEN_ID = -201;
+    private final static long CUSTOM_CONTENT_SCREEN_ID = -301;
 
     private static final long CUSTOM_CONTENT_GESTURE_DELAY = 200;
     private long mTouchDownTime = -1;
@@ -113,10 +120,6 @@
 
     private ShortcutAndWidgetContainer mDragSourceInternal;
 
-    // The screen id used for the empty screen always present to the right.
-    final static long EXTRA_EMPTY_SCREEN_ID = -201;
-    private final static long CUSTOM_CONTENT_SCREEN_ID = -301;
-
     @Thunk LongArrayMap<CellLayout> mWorkspaceScreens = new LongArrayMap<>();
     @Thunk ArrayList<Long> mScreenOrder = new ArrayList<Long>();
 
@@ -136,9 +139,6 @@
     private int mDragOverX = -1;
     private int mDragOverY = -1;
 
-    static Rect mLandscapeCellLayoutMetrics = null;
-    static Rect mPortraitCellLayoutMetrics = null;
-
     CustomContentCallbacks mCustomContentCallbacks;
     boolean mCustomContentShowing;
     private float mLastCustomContentScrollProgress = -1f;
@@ -164,12 +164,11 @@
 
     // 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.
-    private int[] mTempCell = new int[2];
-    private int[] mTempPt = new int[2];
-    private int[] mTempEstimate = new int[2];
+    private static final Rect sTempRect = new Rect();
+    private final int[] mTempXY = new int[2];
     @Thunk float[] mDragViewVisualCenter = new float[2];
     private float[] mTempCellLayoutCenterCoordinates = new float[2];
-    private Matrix mTempInverseMatrix = new Matrix();
+    private int[] mTempVisiblePagesRange = new int[2];
 
     private SpringLoadedDragController mSpringLoadedDragController;
     private float mSpringLoadedShrinkFactor;
@@ -200,7 +199,6 @@
     private boolean mIsSwitchingState = false;
 
     boolean mAnimatingViewIntoPlace = false;
-    boolean mIsDragOccuring = false;
     boolean mChildrenLayersEnabled = true;
 
     private boolean mStripScreensOnPageStopMoving = false;
@@ -210,9 +208,6 @@
 
     private HolographicOutlineHelper mOutlineHelper;
     @Thunk Bitmap mDragOutline = null;
-    private static final Rect sTempRect = new Rect();
-    private final int[] mTempXY = new int[2];
-    private int[] mTempVisiblePagesRange = new int[2];
     public static final int DRAG_BITMAP_PADDING = 2;
     private boolean mWorkspaceFadeInAdjacentScreens;
 
@@ -223,7 +218,6 @@
 
     @Thunk Runnable mDelayedResizeRunnable;
     private Runnable mDelayedSnapToPageRunnable;
-    private Point mDisplaySize = new Point();
 
     // Variables relating to the creation of user folders by hovering shortcuts over shortcuts
     private static final int FOLDER_CREATION_TIMEOUT = 0;
@@ -277,6 +271,7 @@
     boolean mStartedSendingScrollEvents;
     boolean mShouldSendPageSettled;
     int mLastOverlaySroll = 0;
+    private boolean mForceDrawAdjacentPages = false;
 
     // Handles workspace state transitions
     private WorkspaceStateTransitionAnimation mStateTransitionAnimation;
@@ -381,12 +376,11 @@
     }
 
     @Override
-    public void onDragStart(final DragSource source, Object info, int dragAction) {
+    public void onDragStart(final DragSource source, ItemInfo info, int dragAction) {
         if (ENFORCE_DRAG_EVENT_ORDER) {
             enfoceDragParity("onDragStart", 0, 0);
         }
 
-        mIsDragOccuring = true;
         updateChildrenLayersEnabled(false);
         mLauncher.lockScreenOrientation();
         mLauncher.onInteractionBegin();
@@ -417,7 +411,6 @@
             removeExtraEmptyScreen(true, mDragSourceInternal != null);
         }
 
-        mIsDragOccuring = false;
         updateChildrenLayersEnabled(false);
         mLauncher.unlockScreenOrientation(false);
 
@@ -445,8 +438,6 @@
         setupLayoutTransition();
 
         mWallpaperOffset = new WallpaperOffsetInterpolator();
-        Display display = mLauncher.getWindowManager().getDefaultDisplay();
-        display.getSize(mDisplaySize);
 
         mMaxDistanceForFolderCreation = (0.55f * grid.iconSizePx);
 
@@ -578,6 +569,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);
@@ -1318,12 +1310,12 @@
     protected void setWallpaperDimension() {
         new AsyncTask<Void, Void, Void>() {
             public Void doInBackground(Void ... args) {
-                String spKey = LauncherFiles.WALLPAPER_CROP_PREFERENCES_KEY;
-                SharedPreferences sp =
-                        mLauncher.getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS);
-                WallpaperUtils.suggestWallpaperDimension(mLauncher.getResources(),
-                        sp, mLauncher.getWindowManager(), mWallpaperManager,
-                        mLauncher.overrideWallpaperDimensions());
+                if (Utilities.ATLEAST_KITKAT) {
+                    WallpaperUtils.suggestWallpaperDimension(mLauncher);
+                } else {
+                    WallpaperUtils.suggestWallpaperDimensionPreK(mLauncher,
+                            mLauncher.overrideWallpaperDimensions());
+                }
                 return null;
             }
         }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
@@ -1410,7 +1402,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
@@ -1449,7 +1441,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);
 
@@ -1463,6 +1455,10 @@
             }
         }
 
+        private float wallpaperOffsetForCurrentScroll() {
+            return wallpaperOffsetForScroll(getScrollX());
+        }
+
         private int numEmptyScreensToIgnore() {
             int numScrollingPages = getChildCount() - numCustomPages();
             if (numScrollingPages >= MIN_PARALLAX_PAGE_SPAN && hasExtraEmptyScreen()) {
@@ -1856,8 +1852,18 @@
         updateChildrenLayersEnabled(false);
     }
 
+    @Override
+    protected void getVisiblePages(int[] range) {
+        super.getVisiblePages(range);
+        if (mForceDrawAdjacentPages) {
+            // In overview mode, make sure that the two side pages are visible.
+            range[0] = Utilities.boundInRange(getCurrentPage() - 1, numCustomPages(), range[1]);
+            range[1] = Utilities.boundInRange(getCurrentPage() + 1, range[0], getPageCount() - 1);
+        }
+    }
+
     protected void onWallpaperTap(MotionEvent ev) {
-        final int[] position = mTempCell;
+        final int[] position = mTempXY;
         getLocationOnScreen(position);
 
         int pointerIndex = ev.getActionIndex();
@@ -1976,6 +1982,10 @@
         return mState == State.OVERVIEW;
     }
 
+    public void snapToPageFromOverView(int whichPage) {
+        mStateTransitionAnimation.snapToPageFromOverView(whichPage);
+    }
+
     int getOverviewModeTranslationY() {
         DeviceProfile grid = mLauncher.getDeviceProfile();
         Rect workspacePadding = grid.getWorkspacePadding(Utilities.isRtl(getResources()));
@@ -1995,15 +2005,20 @@
      * Sets the current workspace {@link State}, returning an animation transitioning the workspace
      * to that new state.
      */
-    public Animator setStateWithAnimation(State toState, int toPage, boolean animated,
+    public Animator setStateWithAnimation(State toState, boolean animated,
             HashMap<View, Integer> layerViews) {
         // Create the animation to the new state
         Animator workspaceAnim =  mStateTransitionAnimation.getAnimationToState(mState,
-                toState, toPage, animated, layerViews);
+                toState, animated, layerViews);
 
         // Update the current state
         mState = toState;
         updateAccessibilityFlags();
+        if (mState == State.OVERVIEW || mState == State.SPRING_LOADED) {
+            // Redraw pages, as we might want to draw pages which were not visible.
+            mForceDrawAdjacentPages = true;
+            invalidate(); // This will call dispatchDraw(), which calls getVisiblePages().
+        }
 
         return workspaceAnim;
     }
@@ -2076,6 +2091,7 @@
         mIsSwitchingState = false;
         updateChildrenLayersEnabled(false);
         showCustomContentIfNecessary();
+        mForceDrawAdjacentPages = false;
     }
 
     void updateCustomContentVisibility() {
@@ -2122,19 +2138,17 @@
      * @param padding the horizontal and vertical padding to use when drawing
      */
     private static void drawDragView(View v, Canvas destCanvas, int padding) {
-        final Rect clipRect = sTempRect;
-        v.getDrawingRect(clipRect);
-
-        boolean textVisible = false;
-
         destCanvas.save();
         if (v instanceof TextView) {
             Drawable d = getTextViewIcon((TextView) v);
             Rect bounds = getDrawableBounds(d);
-            clipRect.set(0, 0, bounds.width() + padding, bounds.height() + padding);
             destCanvas.translate(padding / 2 - bounds.left, padding / 2 - bounds.top);
             d.draw(destCanvas);
         } else {
+            final Rect clipRect = sTempRect;
+            v.getDrawingRect(clipRect);
+
+            boolean textVisible = false;
             if (v instanceof FolderIcon) {
                 // For FolderIcons the text can bleed into the icon area, and so we need to
                 // hide the text completely (which can't be achieved by clipping).
@@ -2312,7 +2326,8 @@
             icon.clearPressedBackground();
         }
 
-        if (child.getTag() == null || !(child.getTag() instanceof ItemInfo)) {
+        Object dragObject = child.getTag();
+        if (!(dragObject instanceof ItemInfo)) {
             String msg = "Drag started with a view that has no tag set. This "
                     + "will cause a crash (issue 11627249) down the line. "
                     + "View: " + child + "  tag: " + child.getTag();
@@ -2323,8 +2338,9 @@
             mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
         }
 
-        DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
-                DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale, accessible);
+        DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source,
+                (ItemInfo) dragObject, DragController.DRAG_ACTION_MOVE, dragVisualizeOffset,
+                dragRect, scale, accessible);
         dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
 
         b.recycle();
@@ -2360,7 +2376,8 @@
         Point dragVisualizeOffset = new Point(-padding.get() / 2, padding.get() / 2);
         Rect dragRect = new Rect(0, 0, iconSize, iconSize);
 
-        if (child.getTag() == null || !(child.getTag() instanceof ItemInfo)) {
+        Object dragObject = child.getTag();
+        if (!(dragObject instanceof ItemInfo)) {
             String msg = "Drag started with a view that has no tag set. This "
                     + "will cause a crash (issue 11627249) down the line. "
                     + "View: " + child + "  tag: " + child.getTag();
@@ -2368,8 +2385,9 @@
         }
 
         // Start the drag
-        DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
-                DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale, false);
+        DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source,
+                (ItemInfo) dragObject, DragController.DRAG_ACTION_MOVE, dragVisualizeOffset,
+                dragRect, scale, false);
         dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
 
         // Recycle temporary bitmaps
@@ -2400,7 +2418,7 @@
             if (mLauncher.isHotseatLayout(dropTargetLayout)) {
                 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
             } else {
-                mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
+                mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter);
             }
 
             int spanX = 1;
@@ -2410,9 +2428,8 @@
                 spanX = dragCellInfo.spanX;
                 spanY = dragCellInfo.spanY;
             } else {
-                final ItemInfo dragInfo = (ItemInfo) d.dragInfo;
-                spanX = dragInfo.spanX;
-                spanY = dragInfo.spanY;
+                spanX = d.dragInfo.spanX;
+                spanY = d.dragInfo.spanY;
             }
 
             int minSpanX = spanX;
@@ -2427,12 +2444,12 @@
                     mTargetCell);
             float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
                     mDragViewVisualCenter[1], mTargetCell);
-            if (mCreateUserFolderOnDrop && willCreateUserFolder((ItemInfo) d.dragInfo,
+            if (mCreateUserFolderOnDrop && willCreateUserFolder(d.dragInfo,
                     dropTargetLayout, mTargetCell, distance, true)) {
                 return true;
             }
 
-            if (mAddToExistingFolderOnDrop && willAddToExistingUserFolder((ItemInfo) d.dragInfo,
+            if (mAddToExistingFolderOnDrop && willAddToExistingUserFolder(d.dragInfo,
                     dropTargetLayout, mTargetCell, distance)) {
                 return true;
             }
@@ -2498,7 +2515,7 @@
         return (aboveShortcut && willBecomeShortcut);
     }
 
-    boolean willAddToExistingUserFolder(Object dragInfo, CellLayout target, int[] targetCell,
+    boolean willAddToExistingUserFolder(ItemInfo dragInfo, CellLayout target, int[] targetCell,
             float distance) {
         if (distance > mMaxDistanceForFolderCreation) return false;
         View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
@@ -2607,11 +2624,11 @@
             if (mLauncher.isHotseatLayout(dropTargetLayout)) {
                 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
             } else {
-                mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
+                mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter);
             }
         }
 
-        int snapScreen = WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE;
+        int snapScreen = -1;
         boolean resizeOnDrop = false;
         if (d.dragSource != this) {
             final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
@@ -2654,7 +2671,7 @@
 
                 // Aside from the special case where we're dropping a shortcut onto a shortcut,
                 // we need to find the nearest cell location that is vacant
-                ItemInfo item = (ItemInfo) d.dragInfo;
+                ItemInfo item = d.dragInfo;
                 int minSpanX = item.spanX;
                 int minSpanY = item.spanY;
                 if (item.minSpanX > 0 && item.minSpanY > 0) {
@@ -2692,7 +2709,7 @@
                         CellLayout parentCell = getParentCellLayoutForView(cell);
                         if (parentCell != null) {
                             parentCell.removeView(cell);
-                        } else if (LauncherAppState.isDogfoodBuild()) {
+                        } else if (ProviderConfig.IS_DOGFOOD_BUILD) {
                             throw new NullPointerException("mDragInfo.cell has null parent");
                         }
                         addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1],
@@ -2772,9 +2789,7 @@
                     animateWidgetDrop(info, parent, d.dragView,
                             onCompleteRunnable, animationType, cell, false);
                 } else {
-                    int duration = snapScreen < 0 ?
-                            WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE :
-                                    ADJACENT_SCREEN_DROP_DURATION;
+                    int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION;
                     mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
                             onCompleteRunnable, this);
                 }
@@ -2840,45 +2855,6 @@
         }
     }
 
-    /** 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
     public void onDragExit(DragObject d) {
         if (ENFORCE_DRAG_EVENT_ORDER) {
@@ -3017,41 +2993,27 @@
     *
     * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
     * coordinate space. The argument xy is modified with the return result.
-    *
-    * if cachedInverseMatrix is not null, this method will just use that matrix instead of
-    * computing it itself; we use this to avoid redundant matrix inversions in
-    * findMatchingPageForDragOver
-    *
     */
-   void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) {
+   void mapPointFromSelfToChild(View v, float[] xy) {
        xy[0] = xy[0] - v.getLeft();
        xy[1] = xy[1] - v.getTop();
    }
 
-   boolean isPointInSelfOverHotseat(int x, int y, Rect r) {
-       if (r == null) {
-           r = new Rect();
-       }
-       mTempPt[0] = x;
-       mTempPt[1] = y;
-       mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true);
-
-       DeviceProfile grid = mLauncher.getDeviceProfile();
-       r = grid.getHotseatRect();
-       if (r.contains(mTempPt[0], mTempPt[1])) {
-           return true;
-       }
-       return false;
+   boolean isPointInSelfOverHotseat(int x, int y) {
+       mTempXY[0] = x;
+       mTempXY[1] = y;
+       mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY, true);
+       return mLauncher.getDeviceProfile().isInHotseatRect(mTempXY[0], mTempXY[1]);
    }
 
    void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) {
-       mTempPt[0] = (int) xy[0];
-       mTempPt[1] = (int) xy[1];
-       mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true);
-       mLauncher.getDragLayer().mapCoordInSelfToDescendent(hotseat.getLayout(), mTempPt);
+       mTempXY[0] = (int) xy[0];
+       mTempXY[1] = (int) xy[1];
+       mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY, true);
+       mLauncher.getDragLayer().mapCoordInSelfToDescendent(hotseat.getLayout(), mTempXY);
 
-       xy[0] = mTempPt[0];
-       xy[1] = mTempPt[1];
+       xy[0] = mTempXY[0];
+       xy[1] = mTempXY[1];
    }
 
    /*
@@ -3097,10 +3059,7 @@
             CellLayout cl = (CellLayout) getChildAt(i);
 
             final float[] touchXy = {originX, originY};
-            // Transform the touch coordinates to the CellLayout's local coordinates
-            // If the touch point is within the bounds of the cell layout, we can return immediately
-            cl.getMatrix().invert(mTempInverseMatrix);
-            mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix);
+            mapPointFromSelfToChild(cl, touchXy);
 
             if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
                     touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) {
@@ -3142,11 +3101,10 @@
         // Skip drag over events while we are dragging over side pages
         if (mInScrollArea || !transitionStateShouldAllowDrop()) return;
 
-        Rect r = new Rect();
         CellLayout layout = null;
-        ItemInfo item = (ItemInfo) d.dragInfo;
+        ItemInfo item = d.dragInfo;
         if (item == null) {
-            if (LauncherAppState.isDogfoodBuild()) {
+            if (ProviderConfig.IS_DOGFOOD_BUILD) {
                 throw new NullPointerException("DragObject has null info");
             }
             return;
@@ -3160,7 +3118,7 @@
         // Identify whether we have dragged over a side page
         if (workspaceInModalState()) {
             if (mLauncher.getHotseat() != null && !isExternalDragWidget(d)) {
-                if (isPointInSelfOverHotseat(d.x, d.y, r)) {
+                if (isPointInSelfOverHotseat(d.x, d.y)) {
                     layout = mLauncher.getHotseat().getLayout();
                 }
             }
@@ -3183,7 +3141,7 @@
         } else {
             // Test to see if we are over the hotseat otherwise just use the current page
             if (mLauncher.getHotseat() != null && !isDragWidget(d)) {
-                if (isPointInSelfOverHotseat(d.x, d.y, r)) {
+                if (isPointInSelfOverHotseat(d.x, d.y)) {
                     layout = mLauncher.getHotseat().getLayout();
                 }
             }
@@ -3202,10 +3160,10 @@
             if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
                 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
             } else {
-                mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null);
+                mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter);
             }
 
-            ItemInfo info = (ItemInfo) d.dragInfo;
+            ItemInfo info = d.dragInfo;
 
             int minSpanX = item.spanX;
             int minSpanY = item.spanY;
@@ -3385,24 +3343,6 @@
     }
 
     /**
-     * Add the item specified by dragInfo to the given layout.
-     * @return true if successful
-     */
-    public boolean addExternalItemToScreen(ItemInfo dragInfo, CellLayout layout) {
-        if (layout.findCellForSpan(mTempEstimate, dragInfo.spanX, dragInfo.spanY)) {
-            onDropExternal(dragInfo.dropPos, (ItemInfo) dragInfo, (CellLayout) layout, false);
-            return true;
-        }
-        mLauncher.showOutOfSpaceMessage(mLauncher.isHotseatLayout(layout));
-        return false;
-    }
-
-    private void onDropExternal(int[] touchXY, Object dragInfo,
-            CellLayout cellLayout, boolean insertAtFirst) {
-        onDropExternal(touchXY, dragInfo, cellLayout, insertAtFirst, null);
-    }
-
-    /**
      * Drop an item that didn't originate on one of the workspace screens.
      * It may have come from Launcher (e.g. from all apps or customize), or it may have
      * come from another app altogether.
@@ -3410,7 +3350,7 @@
      * NOTE: This can also be called when we are outside of a drag event, when we want
      * to add an item to one of the workspace screens.
      */
-    private void onDropExternal(final int[] touchXY, final Object dragInfo,
+    private void onDropExternal(final int[] touchXY, final ItemInfo dragInfo,
             final CellLayout cellLayout, boolean insertAtFirst, DragObject d) {
         final Runnable exitSpringLoadedRunnable = new Runnable() {
             @Override
@@ -3420,7 +3360,7 @@
             }
         };
 
-        ItemInfo info = (ItemInfo) dragInfo;
+        ItemInfo info = dragInfo;
         int spanX = info.spanX;
         int spanY = info.spanY;
         if (mDragInfo != null) {
@@ -3447,14 +3387,14 @@
                         cellLayout, mTargetCell);
                 float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
                         mDragViewVisualCenter[1], mTargetCell);
-                if (willCreateUserFolder((ItemInfo) d.dragInfo, cellLayout, mTargetCell,
-                        distance, true) || willAddToExistingUserFolder((ItemInfo) d.dragInfo,
-                                cellLayout, mTargetCell, distance)) {
+                if (willCreateUserFolder(d.dragInfo, cellLayout, mTargetCell, distance, true)
+                        || willAddToExistingUserFolder(
+                                d.dragInfo, cellLayout, mTargetCell, distance)) {
                     findNearestVacantCell = false;
                 }
             }
 
-            final ItemInfo item = (ItemInfo) d.dragInfo;
+            final ItemInfo item = d.dragInfo;
             boolean updateWidgetSize = false;
             if (findNearestVacantCell) {
                 int minSpanX = item.spanX;
@@ -3772,7 +3712,7 @@
                     mDragInfo.container, mDragInfo.screenId);
             if (cellLayout != null) {
                 cellLayout.onDropChild(mDragInfo.cell);
-            } else if (LauncherAppState.isDogfoodBuild()) {
+            } else if (ProviderConfig.IS_DOGFOOD_BUILD) {
                 throw new RuntimeException("Invalid state: cellLayout == null in "
                         + "Workspace#onDropCompleted. Please file a bug. ");
             };
@@ -3792,7 +3732,7 @@
         CellLayout parentCell = getParentCellLayoutForView(v);
         if (parentCell != null) {
             parentCell.removeView(v);
-        } else if (LauncherAppState.isDogfoodBuild()) {
+        } else if (ProviderConfig.IS_DOGFOOD_BUILD) {
             // When an app is uninstalled using the drop target, we wait until resume to remove
             // the icon. We also remove all the corresponding items from the workspace at
             // {@link Launcher#bindComponentsRemoved}. That call can come before or after
@@ -3819,29 +3759,6 @@
         }
     }
 
-    void updateItemLocationsInDatabase(CellLayout cl) {
-        int count = cl.getShortcutsAndWidgets().getChildCount();
-
-        long screenId = getIdForScreen(cl);
-        int container = Favorites.CONTAINER_DESKTOP;
-
-        if (mLauncher.isHotseatLayout(cl)) {
-            screenId = -1;
-            container = Favorites.CONTAINER_HOTSEAT;
-        }
-
-        for (int i = 0; i < count; i++) {
-            View v = cl.getShortcutsAndWidgets().getChildAt(i);
-            ItemInfo info = (ItemInfo) v.getTag();
-            // Null check required as the AllApps button doesn't have an item info
-            if (info != null && info.requiresDbUpdate) {
-                info.requiresDbUpdate = false;
-                LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, info.cellX,
-                        info.cellY, info.spanX, info.spanY);
-            }
-        }
-    }
-
     void saveWorkspaceToDb() {
         saveWorkspaceScreenToDb((CellLayout) mLauncher.getHotseat().getLayout());
         int count = getChildCount();
@@ -4089,7 +4006,7 @@
 
             @Override
             public boolean evaluate(ItemInfo info, View v, View parent) {
-                return info.id == id;
+                return info != null && info.id == id;
             }
         });
     }
@@ -4468,7 +4385,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);
     }
 
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 54f63bb..011f4a2 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;
@@ -174,7 +175,6 @@
 
     public static final String TAG = "WorkspaceStateTransitionAnimation";
 
-    public static final int SCROLL_TO_CURRENT_PAGE = -1;
     @Thunk static final int BACKGROUND_FADE_OUT_DURATION = 350;
 
     final @Thunk Launcher mLauncher;
@@ -217,14 +217,18 @@
         mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
     }
 
+    public void snapToPageFromOverView(int whichPage) {
+        mWorkspace.snapToPage(whichPage, mOverviewTransitionTime, mZoomInInterpolator);
+    }
+
     public AnimatorSet getAnimationToState(Workspace.State fromState, Workspace.State toState,
-            int toPage, boolean animated, HashMap<View, Integer> layerViews) {
+            boolean animated, HashMap<View, Integer> layerViews) {
         AccessibilityManager am = (AccessibilityManager)
                 mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE);
         final boolean accessibilityEnabled = am.isEnabled();
         TransitionStates states = new TransitionStates(fromState, toState);
         int workspaceDuration = getAnimationDuration(states);
-        animateWorkspace(states, toPage, animated, workspaceDuration, layerViews,
+        animateWorkspace(states, animated, workspaceDuration, layerViews,
                 accessibilityEnabled);
         animateBackgroundGradient(states, animated, BACKGROUND_FADE_OUT_DURATION);
         return mStateAnimator;
@@ -263,7 +267,7 @@
     /**
      * Starts a transition animation for the workspace.
      */
-    private void animateWorkspace(final TransitionStates states, int toPage, final boolean animated,
+    private void animateWorkspace(final TransitionStates states, final boolean animated,
                                   final int duration, final HashMap<View, Integer> layerViews,
                                   final boolean accessibilityEnabled) {
         // Reinitialize animation arrays for the current workspace state
@@ -303,11 +307,7 @@
             }
         }
 
-        if (toPage == SCROLL_TO_CURRENT_PAGE) {
-            toPage = mWorkspace.getPageNearestToCenterOfScreen();
-        }
-        mWorkspace.snapToPage(toPage, duration, mZoomInInterpolator);
-
+        int toPage = mWorkspace.getPageNearestToCenterOfScreen();
         for (int i = 0; i < childCount; i++) {
             final CellLayout cl = (CellLayout) mWorkspace.getChildAt(i);
             boolean isCurrentPage = (i == toPage);
@@ -379,7 +379,6 @@
                             mNewBackgroundAlphas[i] != 0) {
                         ValueAnimator bgAnim = ObjectAnimator.ofFloat(cl, "backgroundAlpha",
                                 mOldBackgroundAlphas[i], mNewBackgroundAlphas[i]);
-                                LauncherAnimUtils.ofFloat(cl, 0f, 1f);
                         bgAnim.setInterpolator(mZoomInInterpolator);
                         bgAnim.setDuration(duration);
                         mStateAnimator.play(bgAnim);
@@ -488,8 +487,7 @@
             if (animated) {
                 // These properties refer to the background protection gradient used for AllApps
                 // and Widget tray.
-                ValueAnimator bgFadeOutAnimation =
-                        LauncherAnimUtils.ofFloat(mWorkspace, startAlpha, finalAlpha);
+                ValueAnimator bgFadeOutAnimation = ValueAnimator.ofFloat(startAlpha, finalAlpha);
                 bgFadeOutAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                     @Override
                     public void onAnimationUpdate(ValueAnimator animation) {
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index fe7b25e..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;
@@ -375,7 +375,7 @@
 
 
     @Override
-    public void onDragStart(DragSource source, Object info, int dragAction) {
+    public void onDragStart(DragSource source, ItemInfo info, int dragAction) {
         // No-op
     }
 
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 564527e..41b4e04 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -532,7 +532,7 @@
                 int currentScreen = mLauncher.getCurrentWorkspaceScreen();
                 Workspace workspace = (Workspace) target;
                 CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
-                ItemInfo itemInfo = (ItemInfo) d.dragInfo;
+                ItemInfo itemInfo = d.dragInfo;
                 if (layout != null) {
                     showOutOfSpaceMessage =
                             !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 1f95133..f885567 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -319,7 +319,7 @@
         }
     }
 
-    private Launcher mLauncher;
+    @Thunk Launcher mLauncher;
     private LayoutInflater mLayoutInflater;
     @Thunk AlphabeticalAppsList mApps;
     private GridLayoutManager mGridLayoutMgr;
@@ -342,9 +342,9 @@
     // each time the search query changes.
     private String mMarketSearchMessage;
     // The intent to send off to the market app, updated each time the search query changes.
-    private Intent mMarketSearchIntent;
+    @Thunk Intent mMarketSearchIntent;
     // The last query that the user entered into the search field
-    private String mLastSearchQuery;
+    @Thunk String mLastSearchQuery;
 
     // Section drawing
     @Thunk int mSectionNamesMargin;
@@ -414,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);
         }
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java
index 14e2a18..09a7d59 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java
@@ -17,17 +17,15 @@
 
 import android.content.Context;
 import android.graphics.Bitmap;
-import android.graphics.Canvas;
 import android.util.AttributeSet;
-import android.view.View;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
+
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler;
 import com.android.launcher3.ClickShadowView;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
 
 /**
  * A container for RecyclerView to allow for the click shadow view to be shown behind an icon that
diff --git a/src/com/android/launcher3/allapps/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/AllAppsSearchBarController.java
index 2b363c0..d853d5b 100644
--- a/src/com/android/launcher3/allapps/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/AllAppsSearchBarController.java
@@ -15,7 +15,6 @@
  */
 package com.android.launcher3.allapps;
 
-import android.content.ComponentName;
 import android.graphics.Rect;
 import android.view.View;
 import android.view.ViewGroup;
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index dac0df1..cb989e5 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -16,13 +16,13 @@
 package com.android.launcher3.allapps;
 
 import android.content.Context;
-import android.support.v7.widget.RecyclerView;
 import android.util.Log;
+
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.compat.AlphabeticIndexCompat;
 import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.config.ProviderConfig;
 import com.android.launcher3.model.AppNameComparator;
 import com.android.launcher3.util.ComponentKey;
 
@@ -186,7 +186,7 @@
     // The of ordered component names as a result of a search query
     private ArrayList<ComponentKey> mSearchResults;
     private HashMap<CharSequence, String> mCachedSectionNames = new HashMap<>();
-    private RecyclerView.Adapter mAdapter;
+    private AllAppsGridAdapter mAdapter;
     private AlphabeticIndexCompat mIndexer;
     private AppNameComparator mAppNameComparator;
     private MergeAlgorithm mMergeAlgorithm;
@@ -215,7 +215,7 @@
     /**
      * Sets the adapter to notify when this dataset changes.
      */
-    public void setAdapter(RecyclerView.Adapter adapter) {
+    public void setAdapter(AllAppsGridAdapter adapter) {
         mAdapter = adapter;
     }
 
@@ -419,7 +419,7 @@
                 if (info != null) {
                     mPredictedApps.add(info);
                 } else {
-                    if (LauncherAppState.isDogfoodBuild()) {
+                    if (ProviderConfig.IS_DOGFOOD_BUILD) {
                         Log.e(TAG, "Predicted app not found: " + ck.flattenToString(mLauncher));
                     }
                 }
diff --git a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
index ec1fb66..463278a 100644
--- a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
+++ b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
@@ -1,6 +1,7 @@
 package com.android.launcher3.compat;
 
 import android.content.Context;
+
 import com.android.launcher3.Utilities;
 
 import java.lang.reflect.Constructor;
@@ -62,6 +63,7 @@
     private boolean mHasValidAlphabeticIndex;
     private String mDefaultMiscLabel;
 
+    @SuppressWarnings({"unchecked", "rawtypes"})
     public AlphabeticIndexCompat(Context context) {
         super();
         try {
diff --git a/src/com/android/launcher3/config/ProviderConfig.java b/src/com/android/launcher3/config/ProviderConfig.java
index e8930d0..825b434 100644
--- a/src/com/android/launcher3/config/ProviderConfig.java
+++ b/src/com/android/launcher3/config/ProviderConfig.java
@@ -19,4 +19,6 @@
 public class ProviderConfig {
 
     public static final String AUTHORITY = "com.android.launcher3.settings".intern();
+
+    public static boolean IS_DOGFOOD_BUILD = false;
 }
diff --git a/src/com/android/launcher3/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
similarity index 83%
rename from src/com/android/launcher3/DragController.java
rename to src/com/android/launcher3/dragndrop/DragController.java
index 2191455..8777dc6 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;
@@ -26,6 +26,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.util.Log;
+import android.view.DragEvent;
 import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -34,15 +35,25 @@
 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.Utilities;
 import com.android.launcher3.util.Thunk;
 
+import com.android.launcher3.R;
+
 import java.util.ArrayList;
 import java.util.HashSet;
 
 /**
  * Class for initiating a drag within a view or across multiple views.
  */
-public class DragController {
+public class DragController implements DragDriver.EventListener {
     private static final String TAG = "Launcher.DragController";
 
     /** Indicates the drag is a move.  */
@@ -59,9 +70,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;
 
@@ -73,10 +84,13 @@
     private final int[] mCoordinatesTemp = new int[2];
     private final boolean mIsRtl;
 
-    /** Whether or not we're dragging. */
-    private boolean mDragging;
+    /**
+     * Drag driver for the current drag/drop operation, or null if there is no active DND operation.
+     * It's null during accessible drag operations.
+     */
+    private DragDriver mDragDriver = null;
 
-    /** Whether or not this is an accessible drag operation */
+    /** Whether or not an accessible drag operation is in progress. */
     private boolean mIsAccessibleDrag;
 
     /** X coordinate of the down event. */
@@ -88,7 +102,7 @@
     /** the area at the edge of the screen that makes the workspace go left
      *   or right while you're dragging.
      */
-    private int mScrollZone;
+    private final int mScrollZone;
 
     private DropTarget.DragObject mDragObject;
 
@@ -120,7 +134,7 @@
     private int mTmpPoint[] = new int[2];
     private Rect mDragLayerRect = new Rect();
 
-    protected int mFlingToDeleteThresholdVelocity;
+    protected final int mFlingToDeleteThresholdVelocity;
     private VelocityTracker mVelocityTracker;
 
     /**
@@ -135,14 +149,14 @@
          * @param dragAction The drag action: either {@link DragController#DRAG_ACTION_MOVE}
          *        or {@link DragController#DRAG_ACTION_COPY}
          */
-        void onDragStart(DragSource source, Object info, int dragAction);
+        void onDragStart(DragSource source, ItemInfo info, int dragAction);
 
         /**
          * The drag has ended
          */
         void onDragEnd();
     }
-    
+
     /**
      * Used to create a new DragLayer from XML.
      *
@@ -155,16 +169,11 @@
         mScrollZone = r.getDimensionPixelSize(R.dimen.scroll_zone);
         mVelocityTracker = VelocityTracker.obtain();
 
-        float density = r.getDisplayMetrics().density;
         mFlingToDeleteThresholdVelocity =
-                (int) (r.getInteger(R.integer.config_flingToDeleteMinVelocity) * density);
+                r.getDimensionPixelSize(R.dimen.drag_flingToDeleteMinVelocity);
         mIsRtl = Utilities.isRtl(r);
     }
 
-    public boolean dragging() {
-        return mDragging;
-    }
-
     /**
      * Starts a drag.
      *
@@ -178,7 +187,7 @@
      * @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
      *          Makes dragging feel more precise, e.g. you can clip out a transparent border
      */
-    public void startDrag(View v, Bitmap bmp, DragSource source, Object dragInfo,
+    public void startDrag(View v, Bitmap bmp, DragSource source, ItemInfo dragInfo,
             Rect viewImageBounds, int dragAction, float initialDragViewScale) {
         int[] loc = mCoordinatesTemp;
         mLauncher.getDragLayer().getLocationInDragLayer(v, loc);
@@ -211,7 +220,7 @@
      * @param accessible whether this drag should occur in accessibility mode
      */
     public DragView startDrag(Bitmap b, int dragLayerX, int dragLayerY,
-            DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion,
+            DragSource source, ItemInfo dragInfo, int dragAction, Point dragOffset, Rect dragRegion,
             float initialDragViewScale, boolean accessible) {
         if (PROFILE_DRAWING_DURING_DRAG) {
             android.os.Debug.startMethodTracing("Launcher");
@@ -234,8 +243,8 @@
         final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;
         final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;
 
-        mDragging = true;
         mIsAccessibleDrag = accessible;
+        mLastDropTarget = null;
 
         mDragObject = new DropTarget.DragObject();
 
@@ -256,6 +265,10 @@
         final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
                 registrationY, 0, 0, b.getWidth(), b.getHeight(), initialDragViewScale);
 
+        if (!accessible) {
+            mDragDriver = DragDriver.create(this, dragInfo, dragView);
+        }
+
         if (dragOffset != null) {
             dragView.setDragVisualizeOffset(new Point(dragOffset));
         }
@@ -318,18 +331,18 @@
      * </pre>
      */
     public boolean dispatchKeyEvent(KeyEvent event) {
-        return mDragging;
+        return mDragDriver != null;
     }
 
     public boolean isDragging() {
-        return mDragging;
+        return mDragDriver != null || mIsAccessibleDrag;
     }
 
     /**
      * Stop dragging without dropping.
      */
     public void cancelDrag() {
-        if (mDragging) {
+        if (isDragging()) {
             if (mLastDropTarget != null) {
                 mLastDropTarget.onDragExit(mDragObject);
             }
@@ -340,6 +353,7 @@
         }
         endDrag();
     }
+
     public void onAppsRemoved(final ArrayList<String> packageNames, HashSet<ComponentName> cns) {
         // Cancel the current drag if we are removing an app that we are dragging
         if (mDragObject != null) {
@@ -363,8 +377,8 @@
     }
 
     private void endDrag() {
-        if (mDragging) {
-            mDragging = false;
+        if (isDragging()) {
+            mDragDriver = null;
             mIsAccessibleDrag = false;
             clearScrollRunnable();
             boolean isDeferred = false;
@@ -415,18 +429,66 @@
         return mTmpPoint;
     }
 
-    long getLastGestureUpTime() {
-        if (mDragging) {
+    public long getLastGestureUpTime() {
+        if (mDragDriver != null) {
             return System.currentTimeMillis();
         } else {
             return mLastTouchUpTime;
         }
     }
 
-    void resetLastGestureUpTime() {
+    public void resetLastGestureUpTime() {
         mLastTouchUpTime = -1;
     }
 
+    @Override
+    public void onDriverDragMove(float x, float y) {
+        final int[] dragLayerPos = getClampedDragLayerPos(x, y);
+
+        handleMoveEvent(dragLayerPos[0], dragLayerPos[1]);
+    }
+
+    @Override
+    public void onDriverDragExitWindow() {
+        if (mLastDropTarget != null) {
+            mLastDropTarget.onDragExit(mDragObject);
+            mLastDropTarget = null;
+        }
+    }
+
+    @Override
+    public void onDriverDragEnd(float x, float y, DropTarget dropTargetOverride) {
+        final int[] dragLayerPos = getClampedDragLayerPos(x, y);
+        final int dragLayerX = dragLayerPos[0];
+        final int dragLayerY = dragLayerPos[1];
+
+        DropTarget dropTarget;
+        PointF vec = null;
+
+        if (dropTargetOverride != null) {
+            dropTarget = dropTargetOverride;
+        } else {
+            vec = isFlingingToDelete(mDragObject.dragSource);
+            if (!DeleteDropTarget.supportsDrop(mDragObject.dragInfo)) {
+                vec = null;
+            }
+            if (vec != null) {
+                dropTarget = mFlingToDeleteDropTarget;
+            } else {
+                dropTarget = findDropTarget((int) x, (int) y, mCoordinatesTemp);
+            }
+        }
+
+        drop(dropTarget, x, y, vec);
+
+        endDrag();
+    }
+
+    @Override
+    public void onDriverDragCancel() {
+        cancelDrag();
+    }
+
     /**
      * Call this from a drag source view.
      */
@@ -434,8 +496,8 @@
         @SuppressWarnings("all") // suppress dead code warning
         final boolean debug = false;
         if (debug) {
-            Log.d(Launcher.TAG, "DragController.onInterceptTouchEvent " + ev + " mDragging="
-                    + mDragging);
+            Log.d(Launcher.TAG, "DragController.onInterceptTouchEvent " + ev + " Dragging="
+                    + (mDragDriver != null));
         }
 
         if (mIsAccessibleDrag) {
@@ -451,41 +513,39 @@
         final int dragLayerY = dragLayerPos[1];
 
         switch (action) {
-            case MotionEvent.ACTION_MOVE:
-                break;
             case MotionEvent.ACTION_DOWN:
                 // Remember location of down touch
                 mMotionDownX = dragLayerX;
                 mMotionDownY = dragLayerY;
-                mLastDropTarget = null;
                 break;
             case MotionEvent.ACTION_UP:
                 mLastTouchUpTime = System.currentTimeMillis();
-                if (mDragging) {
-                    PointF vec = isFlingingToDelete(mDragObject.dragSource);
-                    if (!DeleteDropTarget.supportsDrop(mDragObject.dragInfo)) {
-                        vec = null;
-                    }
-                    if (vec != null) {
-                        dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec);
-                    } else {
-                        drop(dragLayerX, dragLayerY);
-                    }
-                }
-                endDrag();
-                break;
-            case MotionEvent.ACTION_CANCEL:
-                cancelDrag();
                 break;
         }
 
-        return mDragging;
+        return mDragDriver != null && mDragDriver.onInterceptTouchEvent(ev);
+    }
+
+    /**
+     * Call this from a drag source view.
+     */
+    public boolean onDragEvent(DragEvent event) {
+        return mDragDriver != null && mDragDriver.onDragEvent(event);
+    }
+
+    /**
+     * Call this from a drag view.
+     */
+    public void onDragViewAnimationEnd() {
+        if (mDragDriver != null) {
+            mDragDriver.onDragViewAnimationEnd();
+        }
     }
 
     /**
      * Sets the view that should handle move events.
      */
-    void setMoveTarget(View view) {
+    public void setMoveTarget(View view) {
         mMoveTarget = view;
     }    
 
@@ -579,7 +639,7 @@
      * Call this from a drag source view.
      */
     public boolean onTouchEvent(MotionEvent ev) {
-        if (!mDragging || mIsAccessibleDrag) {
+        if (mDragDriver == null || mIsAccessibleDrag) {
             return false;
         }
 
@@ -592,47 +652,25 @@
         final int dragLayerY = dragLayerPos[1];
 
         switch (action) {
-        case MotionEvent.ACTION_DOWN:
-            // Remember where the motion event started
-            mMotionDownX = dragLayerX;
-            mMotionDownY = dragLayerY;
+            case MotionEvent.ACTION_DOWN:
+                // 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;
-            }
-            handleMoveEvent(dragLayerX, dragLayerY);
-            break;
-        case MotionEvent.ACTION_MOVE:
-            handleMoveEvent(dragLayerX, dragLayerY);
-            break;
-        case MotionEvent.ACTION_UP:
-            // Ensure that we've processed a move event at the current pointer location.
-            handleMoveEvent(dragLayerX, dragLayerY);
-            mHandler.removeCallbacks(mScrollRunnable);
-
-            if (mDragging) {
-                PointF vec = isFlingingToDelete(mDragObject.dragSource);
-                if (!DeleteDropTarget.supportsDrop(mDragObject.dragInfo)) {
-                    vec = null;
-                }
-                if (vec != null) {
-                    dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec);
+                if ((dragLayerX < mScrollZone) || (dragLayerX > mScrollView.getWidth() - mScrollZone)) {
+                    mScrollState = SCROLL_WAITING_IN_ZONE;
+                    mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
                 } else {
-                    drop(dragLayerX, dragLayerY);
+                    mScrollState = SCROLL_OUTSIDE_ZONE;
                 }
-            }
-            endDrag();
-            break;
-        case MotionEvent.ACTION_CANCEL:
-            mHandler.removeCallbacks(mScrollRunnable);
-            cancelDrag();
-            break;
+                break;
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                mHandler.removeCallbacks(mScrollRunnable);
+                break;
         }
 
-        return true;
+        return mDragDriver.onTouchEvent(ev);
     }
 
     /**
@@ -642,7 +680,6 @@
     public void prepareAccessibleDrag(int x, int y) {
         mMotionDownX = x;
         mMotionDownY = y;
-        mLastDropTarget = null;
     }
 
     /**
@@ -660,7 +697,7 @@
 
         dropTarget.prepareAccessibilityDrop();
         // Perform the drop
-        drop(location[0], location[1]);
+        drop(dropTarget, location[0], location[1], null);
         endDrag();
     }
 
@@ -690,49 +727,41 @@
         return null;
     }
 
-    private void dropOnFlingToDeleteTarget(float x, float y, PointF vel) {
+    void drop(DropTarget dropTarget, float x, float y, PointF flingVel) {
         final int[] coordinates = mCoordinatesTemp;
 
         mDragObject.x = coordinates[0];
         mDragObject.y = coordinates[1];
 
-        // Clean up dragging on the target if it's not the current fling delete target otherwise,
-        // start dragging to it.
-        if (mLastDropTarget != null && mFlingToDeleteDropTarget != mLastDropTarget) {
-            mLastDropTarget.onDragExit(mDragObject);
+        // Move dragging to the final target.
+        if (dropTarget != mLastDropTarget) {
+            if (mLastDropTarget != null) {
+                mLastDropTarget.onDragExit(mDragObject);
+            }
+            mLastDropTarget = dropTarget;
+            if (dropTarget != null) {
+                dropTarget.onDragEnter(mDragObject);
+            }
         }
 
-        // Drop onto the fling-to-delete target
-        boolean accepted = false;
-        mFlingToDeleteDropTarget.onDragEnter(mDragObject);
-        // We must set dragComplete to true _only_ after we "enter" the fling-to-delete target for
-        // "drop"
         mDragObject.dragComplete = true;
-        mFlingToDeleteDropTarget.onDragExit(mDragObject);
-        if (mFlingToDeleteDropTarget.acceptDrop(mDragObject)) {
-            mFlingToDeleteDropTarget.onFlingToDelete(mDragObject, vel);
-            accepted = true;
-        }
-        mDragObject.dragSource.onDropCompleted((View) mFlingToDeleteDropTarget, mDragObject, true,
-                accepted);
-    }
 
-    private void drop(float x, float y) {
-        final int[] coordinates = mCoordinatesTemp;
-        final DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);
-
-        mDragObject.x = coordinates[0];
-        mDragObject.y = coordinates[1];
+        // Drop onto the target.
         boolean accepted = false;
         if (dropTarget != null) {
-            mDragObject.dragComplete = true;
             dropTarget.onDragExit(mDragObject);
             if (dropTarget.acceptDrop(mDragObject)) {
-                dropTarget.onDrop(mDragObject);
+                if (flingVel != null) {
+                    dropTarget.onFlingToDelete(mDragObject, flingVel);
+                } else {
+                    dropTarget.onDrop(mDragObject);
+                }
                 accepted = true;
             }
         }
-        mDragObject.dragSource.onDropCompleted((View) dropTarget, mDragObject, false, accepted);
+        final View dropTargetAsView = dropTarget instanceof View ? (View) dropTarget : null;
+        mDragObject.dragSource.onDropCompleted(
+                dropTargetAsView, mDragObject, flingVel != null, accepted);
     }
 
     private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) {
@@ -825,10 +854,6 @@
         mScrollView = v;
     }
 
-    DragView getDragView() {
-        return mDragObject.dragView;
-    }
-
     private class ScrollRunnable implements Runnable {
         private int mDirection;
 
diff --git a/src/com/android/launcher3/dragndrop/DragDriver.java b/src/com/android/launcher3/dragndrop/DragDriver.java
new file mode 100644
index 0000000..6e4b430
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/DragDriver.java
@@ -0,0 +1,250 @@
+/*
+ * 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.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;
+import android.graphics.Canvas;
+import android.graphics.Point;
+import android.view.DragEvent;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * Base class for driving a drag/drop operation.
+ */
+public abstract class DragDriver {
+    protected final EventListener mEventListener;
+
+    public interface EventListener {
+        void onDriverDragMove(float x, float y);
+        void onDriverDragExitWindow();
+        void onDriverDragEnd(float x, float y, DropTarget dropTargetOverride);
+        void onDriverDragCancel();
+    }
+
+    public DragDriver(EventListener eventListener) {
+        mEventListener = eventListener;
+    }
+
+    /**
+     * Handles ending of the DragView animation.
+     */
+    public abstract void onDragViewAnimationEnd();
+
+    public boolean onTouchEvent(MotionEvent ev) {
+        final int action = ev.getAction();
+
+        switch (action) {
+            case MotionEvent.ACTION_MOVE:
+                mEventListener.onDriverDragMove(ev.getX(), ev.getY());
+                break;
+            case MotionEvent.ACTION_UP:
+                mEventListener.onDriverDragMove(ev.getX(), ev.getY());
+                mEventListener.onDriverDragEnd(ev.getX(), ev.getY(), null);
+                break;
+            case MotionEvent.ACTION_CANCEL:
+                mEventListener.onDriverDragCancel();
+                break;
+        }
+
+        return true;
+    }
+
+    public abstract boolean onDragEvent (DragEvent event);
+
+
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        final int action = ev.getAction();
+
+        switch (action) {
+            case MotionEvent.ACTION_UP:
+                mEventListener.onDriverDragEnd(ev.getX(), ev.getY(), null);
+                break;
+            case MotionEvent.ACTION_CANCEL:
+                mEventListener.onDriverDragCancel();
+                break;
+        }
+
+        return true;
+    }
+
+    public static DragDriver create(
+            DragController dragController, ItemInfo dragInfo, DragView dragView) {
+        if (Utilities.isNycOrAbove()) {
+            return new SystemDragDriver(dragController, dragInfo.getIntent(), dragView);
+        } else {
+            return new InternalDragDriver(dragController);
+        }
+    }
+
+};
+
+/**
+ * Class for driving a system (i.e. framework) drag/drop operation.
+ */
+class SystemDragDriver extends DragDriver {
+    /** Intent associated with the drag operation, or null is there no associated intent.  */
+    private final Intent mDragIntent;
+
+    private final DragView mDragView;
+    boolean mIsFrameworkDragActive = false;
+    boolean mReceivedDropEvent = false;
+    float mLastX = 0;
+    float mLastY = 0;
+
+    public SystemDragDriver(DragController dragController, Intent dragIntent, DragView dragView) {
+        super(dragController);
+        mDragIntent = dragIntent;
+        mDragView = dragView;
+    }
+
+    private static class ShadowBuilder extends View.DragShadowBuilder {
+        final DragView mDragView;
+
+        public ShadowBuilder(DragView dragView) {
+            mDragView = dragView;
+        }
+
+        @Override
+        public void onProvideShadowMetrics (Point size, Point touch) {
+            mDragView.provideDragShadowMetrics(size, touch);
+        }
+
+        @Override
+        public void onDrawShadow(Canvas canvas) {
+            mDragView.drawDragShadow(canvas);
+        }
+    };
+
+    @Override
+    public void onDragViewAnimationEnd() {
+        // Clip data for the drag operation. If there is an intent, create an intent-based ClipData,
+        // which will be passed to a global DND.
+        // If there is no intent, craft a fake ClipData and start a local DND operation; this
+        // ClipData will be ignored.
+        final ClipData dragData = mDragIntent != null ?
+                ClipData.newIntent("", mDragIntent) :
+                ClipData.newPlainText("", "");
+
+        View.DragShadowBuilder shadowBuilder = new ShadowBuilder(mDragView);
+        // TODO: DND flags are in flux, once settled, use the appropriate constant.
+        final int flagGlobal = 1 << 0;
+        final int flagOpaque = 1 << 9;
+        final int flags = (mDragIntent != null ? flagGlobal : 0) | flagOpaque;
+
+        mIsFrameworkDragActive = true;
+
+        if (!mDragView.startDrag(dragData, shadowBuilder, null, flags)) {
+            mIsFrameworkDragActive = false;
+            mEventListener.onDriverDragCancel();
+            return;
+        }
+
+        // Starting from this point, the driver takes over showing the drag shadow, so hiding the
+        // drag view.
+        mDragView.setVisibility(View.INVISIBLE);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        return !mIsFrameworkDragActive && super.onTouchEvent(ev);
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        return !mIsFrameworkDragActive && super.onInterceptTouchEvent(ev);
+    }
+
+    @Override
+    public boolean onDragEvent (DragEvent event) {
+        if (!mIsFrameworkDragActive) {
+            // We are interested only in drag events started by this driver.
+            return false;
+        }
+
+        final int action = event.getAction();
+
+        switch (action) {
+            case DragEvent.ACTION_DRAG_STARTED:
+                mLastX = event.getX();
+                mLastY = event.getY();
+                return true;
+
+            case DragEvent.ACTION_DRAG_ENTERED:
+                mLastX = event.getX();
+                mLastY = event.getY();
+                return true;
+
+            case DragEvent.ACTION_DRAG_LOCATION:
+                mLastX = event.getX();
+                mLastY = event.getY();
+                mEventListener.onDriverDragMove(event.getX(), event.getY());
+                return true;
+
+            case DragEvent.ACTION_DROP:
+                mLastX = event.getX();
+                mLastY = event.getY();
+                mReceivedDropEvent = true;
+                return true;
+
+            case DragEvent.ACTION_DRAG_EXITED:
+                mLastX = event.getX();
+                mLastY = event.getY();
+                mEventListener.onDriverDragExitWindow();
+                return true;
+
+            case DragEvent.ACTION_DRAG_ENDED:
+                final boolean dragAccepted = event.getResult();
+                final boolean acceptedByAnotherWindow = dragAccepted && !mReceivedDropEvent;
+
+                // When the system drag ends, its drag shadow disappears. Resume showing the drag
+                // view for the possible final animation.
+                mDragView.setVisibility(View.VISIBLE);
+
+                final DropTarget dropTargetOverride = acceptedByAnotherWindow ?
+                        new AnotherWindowDropTarget(mDragView.getContext()) : null;
+
+                mEventListener.onDriverDragEnd(mLastX, mLastY, dropTargetOverride);
+                mIsFrameworkDragActive = false;
+                return true;
+
+            default:
+                return false;
+        }
+    }
+};
+
+/**
+ * Class for driving an internal (i.e. not using framework) drag/drop operation.
+ */
+class InternalDragDriver extends DragDriver {
+    public InternalDragDriver(DragController dragController) {
+        super(dragController);
+    }
+
+    @Override
+    public void onDragViewAnimationEnd() {}
+
+    @Override
+    public boolean onDragEvent (DragEvent event) { return false; }
+};
diff --git a/src/com/android/launcher3/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
similarity index 97%
rename from src/com/android/launcher3/DragLayer.java
rename to src/com/android/launcher3/dragndrop/DragLayer.java
index 1c18747..741d5e6 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;
@@ -28,6 +28,7 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
+import android.view.DragEvent;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
@@ -39,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;
 
 /**
@@ -375,6 +391,11 @@
         return mDragController.onTouchEvent(ev);
     }
 
+    @Override
+    public boolean onDragEvent (DragEvent event) {
+        return mDragController.onDragEvent(event);
+    }
+
     /**
      * Determine the rect of the descendant in this DragLayer's coordinates
      *
@@ -764,7 +785,7 @@
         // Show the drop view if it was previously hidden
         mDropView = view;
         mDropView.cancelAnimation();
-        mDropView.resetLayoutParams();
+        mDropView.requestLayout();
 
         // Set the anchor view if the page is scrolling
         if (anchorView != null) {
@@ -884,7 +905,7 @@
         invalidate();
     }
 
-    void showPageHints() {
+    public void showPageHints() {
         mShowPageHints = true;
         Workspace workspace = mLauncher.getWorkspace();
         getDescendantRectRelativeToSelf(workspace.getChildAt(workspace.numCustomPages()),
@@ -892,7 +913,7 @@
         invalidate();
     }
 
-    void hidePageHints() {
+    public void hidePageHints() {
         mShowPageHints = false;
         invalidate();
     }
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 84%
rename from src/com/android/launcher3/DragView.java
rename to src/com/android/launcher3/dragndrop/DragView.java
index 2acfc61..88e11fa 100644
--- a/src/com/android/launcher3/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -14,11 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.launcher3;
+package com.android.launcher3.dragndrop;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.FloatArrayEvaluator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -33,8 +36,13 @@
 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 {
@@ -45,19 +53,18 @@
     private Bitmap mBitmap;
     private Bitmap mCrossFadeBitmap;
     @Thunk Paint mPaint;
-    private int mRegistrationX;
-    private int mRegistrationY;
+    private final int mRegistrationX;
+    private final int mRegistrationY;
 
     private Point mDragVisualizeOffset = null;
     private Rect mDragRegion = null;
-    private DragLayer mDragLayer = null;
+    private final DragLayer mDragLayer;
+    @Thunk final DragController mDragController;
     private boolean mHasDrawn = false;
     @Thunk float mCrossFadeProgress = 0f;
+    private boolean mAnimationCancelled = false;
 
     ValueAnimator mAnim;
-    @Thunk float mOffsetX = 0.0f;
-    @Thunk float mOffsetY = 0.0f;
-    private float mInitialScale = 1f;
     // The intrinsic icon scale factor is the scale factor for a drag icon over the workspace
     // size.  This is ignored for non-icons.
     private float mIntrinsicIconScale = 1f;
@@ -81,7 +88,7 @@
             int left, int top, int width, int height, final float initialScale) {
         super(launcher);
         mDragLayer = launcher.getDragLayer();
-        mInitialScale = initialScale;
+        mDragController = launcher.getDragController();
 
         final Resources res = getResources();
         final float scaleDps = res.getDimensionPixelSize(R.dimen.dragViewScale);
@@ -99,11 +106,6 @@
             public void onAnimationUpdate(ValueAnimator animation) {
                 final float value = (Float) animation.getAnimatedValue();
 
-                final int deltaX = (int) (-mOffsetX);
-                final int deltaY = (int) (-mOffsetY);
-
-                mOffsetX += deltaX;
-                mOffsetY += deltaY;
                 setScaleX(initialScale + (value * (scale - initialScale)));
                 setScaleY(initialScale + (value * (scale - initialScale)));
                 if (sDragAlpha != 1f) {
@@ -112,9 +114,15 @@
 
                 if (getParent() == null) {
                     animation.cancel();
-                } else {
-                    setTranslationX(getTranslationX() + deltaX);
-                    setTranslationY(getTranslationY() + deltaY);
+                }
+            }
+        });
+
+        mAnim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (!mAnimationCancelled) {
+                    mDragController.onDragViewAnimationEnd();
                 }
             }
         });
@@ -145,10 +153,6 @@
         return mIntrinsicIconScale;
     }
 
-    public float getOffsetY() {
-        return mOffsetY;
-    }
-
     public int getDragRegionLeft() {
         return mDragRegion.left;
     }
@@ -181,30 +185,33 @@
         return mDragRegion;
     }
 
-    public float getInitialScale() {
-        return mInitialScale;
-    }
-
-    public void updateInitialScaleToCurrentScale() {
-        mInitialScale = getScaleX();
-    }
-
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         setMeasuredDimension(mBitmap.getWidth(), mBitmap.getHeight());
     }
 
+    // Draws drag shadow for system DND.
+    @SuppressLint("WrongCall")
+    public void drawDragShadow(Canvas canvas) {
+        final int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
+        canvas.scale(getScaleX(), getScaleY());
+        onDraw(canvas);
+        canvas.restoreToCount(saveCount);
+    }
+
+    // Provides drag shadow metrics for system DND.
+    public void provideDragShadowMetrics(Point size, Point touch) {
+        size.set((int)(mBitmap.getWidth() * getScaleX()), (int)(mBitmap.getHeight() * getScaleY()));
+
+        final float xGrowth = mBitmap.getWidth() * (getScaleX() - 1);
+        final float yGrowth = mBitmap.getHeight() * (getScaleY() - 1);
+        touch.set(
+                mRegistrationX + (int)Math.round(xGrowth / 2),
+                mRegistrationY + (int)Math.round(yGrowth / 2));
+    }
+
     @Override
     protected void onDraw(Canvas canvas) {
-        @SuppressWarnings("all") // suppress dead code warning
-        final boolean debug = false;
-        if (debug) {
-            Paint p = new Paint();
-            p.setStyle(Paint.Style.FILL);
-            p.setColor(0x66ffffff);
-            canvas.drawRect(0, 0, getWidth(), getHeight(), p);
-        }
-
         mHasDrawn = true;
         boolean crossFade = mCrossFadeProgress > 0 && mCrossFadeBitmap != null;
         if (crossFade) {
@@ -214,12 +221,12 @@
         canvas.drawBitmap(mBitmap, 0.0f, 0.0f, mPaint);
         if (crossFade) {
             mPaint.setAlpha((int) (255 * mCrossFadeProgress));
-            canvas.save();
+            final int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
             float sX = (mBitmap.getWidth() * 1.0f) / mCrossFadeBitmap.getWidth();
             float sY = (mBitmap.getHeight() * 1.0f) / mCrossFadeBitmap.getHeight();
             canvas.scale(sX, sY);
             canvas.drawBitmap(mCrossFadeBitmap, 0.0f, 0.0f, mPaint);
-            canvas.restore();
+            canvas.restoreToCount(saveCount);
         }
     }
 
@@ -235,6 +242,7 @@
             @Override
             public void onAnimationUpdate(ValueAnimator animation) {
                 mCrossFadeProgress = animation.getAnimatedFraction();
+                invalidate();
             }
         });
         va.start();
@@ -328,16 +336,12 @@
     }
 
     public void cancelAnimation() {
+        mAnimationCancelled = true;
         if (mAnim != null && mAnim.isRunning()) {
             mAnim.cancel();
         }
     }
 
-    public void resetLayoutParams() {
-        mOffsetX = mOffsetY = 0;
-        requestLayout();
-    }
-
     /**
      * Move the window containing this view.
      *
@@ -345,8 +349,8 @@
      * @param touchY the y coordinate the user touched in DragLayer coordinates
      */
     void move(int touchX, int touchY) {
-        setTranslationX(touchX - mRegistrationX + (int) mOffsetX);
-        setTranslationY(touchY - mRegistrationY + (int) mOffsetY);
+        setTranslationX(touchX - mRegistrationX);
+        setTranslationY(touchY - mRegistrationY);
     }
 
     void remove() {
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/model/PackageItemInfo.java b/src/com/android/launcher3/model/PackageItemInfo.java
index 30f228c..ddc9cbf 100644
--- a/src/com/android/launcher3/model/PackageItemInfo.java
+++ b/src/com/android/launcher3/model/PackageItemInfo.java
@@ -20,8 +20,6 @@
 
 import com.android.launcher3.ItemInfo;
 
-import java.util.Arrays;
-
 /**
  * Represents a {@link Package} in the widget tray section.
  */
@@ -59,7 +57,6 @@
         return "PackageItemInfo(title=" + title + " id=" + this.id
                 + " type=" + this.itemType + " container=" + this.container
                 + " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY
-                + " spanX=" + spanX + " spanY=" + spanY + " dropPos=" + Arrays.toString(dropPos)
-                + " user=" + user + ")";
+                + " spanX=" + spanX + " spanY=" + spanY + " user=" + user + ")";
     }
 }
diff --git a/src/com/android/launcher3/testing/LauncherExtension.java b/src/com/android/launcher3/testing/LauncherExtension.java
index 8702877..b4d7459 100644
--- a/src/com/android/launcher3/testing/LauncherExtension.java
+++ b/src/com/android/launcher3/testing/LauncherExtension.java
@@ -335,7 +335,8 @@
             @Override
             public void onScrollInteractionEnd() {
                 if (mProgress > 25 && mLauncherOverlayCallbacks.enterFullImmersion()) {
-                    ObjectAnimator oa = LauncherAnimUtils.ofFloat(mSearchOverlay, "translationX", 0);
+                    ObjectAnimator oa = LauncherAnimUtils.ofFloat(
+                            mSearchOverlay, View.TRANSLATION_X, 0);
                     oa.addListener(new AnimatorListenerAdapter() {
                         @Override
                         public void onAnimationEnd(Animator arg0) {
diff --git a/src/com/android/launcher3/util/FlingAnimation.java b/src/com/android/launcher3/util/FlingAnimation.java
index 55c5d7d..f82038b 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 {
 
diff --git a/src/com/android/launcher3/util/FocusLogic.java b/src/com/android/launcher3/util/FocusLogic.java
index 696eabe..1c6efbc 100644
--- a/src/com/android/launcher3/util/FocusLogic.java
+++ b/src/com/android/launcher3/util/FocusLogic.java
@@ -22,9 +22,6 @@
 import android.view.ViewGroup;
 
 import com.android.launcher3.CellLayout;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.ShortcutAndWidgetContainer;
 
 import java.util.Arrays;
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/WallpaperUtils.java b/src/com/android/launcher3/util/WallpaperUtils.java
index b9fccbc..a5251e1 100644
--- a/src/com/android/launcher3/util/WallpaperUtils.java
+++ b/src/com/android/launcher3/util/WallpaperUtils.java
@@ -17,13 +17,16 @@
 package com.android.launcher3.util;
 
 import android.annotation.TargetApi;
+import android.app.Activity;
 import android.app.WallpaperManager;
+import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.os.Build;
 import android.view.WindowManager;
 
+import com.android.launcher3.LauncherFiles;
 import com.android.launcher3.Utilities;
 
 /**
@@ -35,28 +38,59 @@
     public static final String WALLPAPER_HEIGHT_KEY = "wallpaper.height";
     public static final float WALLPAPER_SCREENS_SPAN = 2f;
 
-    public static void suggestWallpaperDimension(Resources res,
-            final SharedPreferences sharedPrefs,
-            WindowManager windowManager,
-            final WallpaperManager wallpaperManager, boolean fallBackToDefaults) {
-        final Point defaultWallpaperSize = WallpaperUtils.getDefaultWallpaperSize(res, windowManager);
+    public static void saveWallpaperDimensions(int width, int height, Activity activity) {
+        if (Utilities.ATLEAST_KITKAT) {
+            // From Kitkat onwards, ImageWallpaper does not care about the
+            // desired width and desired height of the wallpaper.
+            return;
+        }
+        String spKey = LauncherFiles.WALLPAPER_CROP_PREFERENCES_KEY;
+        SharedPreferences sp = activity.getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS);
+        SharedPreferences.Editor editor = sp.edit();
+        if (width != 0 && height != 0) {
+            editor.putInt(WALLPAPER_WIDTH_KEY, width);
+            editor.putInt(WALLPAPER_HEIGHT_KEY, height);
+        } else {
+            editor.remove(WALLPAPER_WIDTH_KEY);
+            editor.remove(WALLPAPER_HEIGHT_KEY);
+        }
+        editor.commit();
+        suggestWallpaperDimensionPreK(activity, true);
+    }
+
+    public static void suggestWallpaperDimensionPreK(
+            Activity activity, boolean fallBackToDefaults) {
+        final Point defaultWallpaperSize = getDefaultWallpaperSize(
+                activity.getResources(), activity.getWindowManager());
+
+        SharedPreferences sp = activity.getSharedPreferences(
+                LauncherFiles.WALLPAPER_CROP_PREFERENCES_KEY, Context.MODE_MULTI_PROCESS);
         // If we have saved a wallpaper width/height, use that instead
+        int width = sp.getInt(WALLPAPER_WIDTH_KEY, -1);
+        int height = sp.getInt(WALLPAPER_HEIGHT_KEY, -1);
 
-        int savedWidth = sharedPrefs.getInt(WALLPAPER_WIDTH_KEY, -1);
-        int savedHeight = sharedPrefs.getInt(WALLPAPER_HEIGHT_KEY, -1);
-
-        if (savedWidth == -1 || savedHeight == -1) {
+        if (width == -1 || height == -1) {
             if (!fallBackToDefaults) {
                 return;
             } else {
-                savedWidth = defaultWallpaperSize.x;
-                savedHeight = defaultWallpaperSize.y;
+                width = defaultWallpaperSize.x;
+                height = defaultWallpaperSize.y;
             }
         }
 
-        if (savedWidth != wallpaperManager.getDesiredMinimumWidth() ||
-                savedHeight != wallpaperManager.getDesiredMinimumHeight()) {
-            wallpaperManager.suggestDesiredDimensions(savedWidth, savedHeight);
+        WallpaperManager wm = WallpaperManager.getInstance(activity);
+        if (width != wm.getDesiredMinimumWidth() || height != wm.getDesiredMinimumHeight()) {
+            wm.suggestDesiredDimensions(width, height);
+        }
+    }
+
+    public static void suggestWallpaperDimension(Activity activity) {
+        // Only live wallpapers care about desired size. Update the size to what launcher expects.
+        final Point size = getDefaultWallpaperSize(
+                activity.getResources(), activity.getWindowManager());
+        WallpaperManager wm = WallpaperManager.getInstance(activity);
+        if (size.x != wm.getDesiredMinimumWidth() || size.y != wm.getDesiredMinimumHeight()) {
+            wm.suggestDesiredDimensions(size.x, size.y);
         }
     }
 
@@ -64,7 +98,7 @@
      * As a ratio of screen height, the total distance we want the parallax effect to span
      * horizontally
      */
-    public static float wallpaperTravelToScreenWidthRatio(int width, int height) {
+    private static float wallpaperTravelToScreenWidthRatio(int width, int height) {
         float aspectRatio = width / (float) height;
 
         // At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.5 * screen width
@@ -94,19 +128,10 @@
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
     public static Point getDefaultWallpaperSize(Resources res, WindowManager windowManager) {
         if (sDefaultWallpaperSize == null) {
-            Point minDims = new Point();
-            Point maxDims = new Point();
-            windowManager.getDefaultDisplay().getCurrentSizeRange(minDims, maxDims);
-
-            int maxDim = Math.max(maxDims.x, maxDims.y);
-            int minDim = Math.max(minDims.x, minDims.y);
-
-            if (Utilities.ATLEAST_JB_MR1) {
-                Point realSize = new Point();
-                windowManager.getDefaultDisplay().getRealSize(realSize);
-                maxDim = Math.max(realSize.x, realSize.y);
-                minDim = Math.min(realSize.x, realSize.y);
-            }
+            Point realSize = new Point();
+            windowManager.getDefaultDisplay().getRealSize(realSize);
+            int maxDim = Math.max(realSize.x, realSize.y);
+            int minDim = Math.min(realSize.x, realSize.y);
 
             // We need to ensure that there is enough extra space in the wallpaper
             // for the intended parallax effects
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 461aebb..7607d85 100644
--- a/src/com/android/launcher3/widget/WidgetHostViewLoader.java
+++ b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
@@ -10,13 +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 {
@@ -44,7 +45,7 @@
     }
 
     @Override
-    public void onDragStart(DragSource source, Object info, int dragAction) { }
+    public void onDragStart(DragSource source, ItemInfo info, int dragAction) { }
 
     @Override
     public void onDragEnd() {
diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java
index 0c6ea31..e6059d5 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");
@@ -317,7 +317,7 @@
                 int currentScreen = mLauncher.getCurrentWorkspaceScreen();
                 Workspace workspace = (Workspace) target;
                 CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
-                ItemInfo itemInfo = (ItemInfo) d.dragInfo;
+                ItemInfo itemInfo = d.dragInfo;
                 if (layout != null) {
                     showOutOfSpaceMessage =
                             !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java
index f1cde29..ac9d62e 100644
--- a/src/com/android/launcher3/widget/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java
@@ -27,7 +27,6 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
-import android.view.ViewGroup.MarginLayoutParams;
 import android.widget.LinearLayout;
 
 import com.android.launcher3.BubbleTextView;
@@ -65,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) {
@@ -207,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/WidgetsRowViewHolder.java b/src/com/android/launcher3/widget/WidgetsRowViewHolder.java
index 249559a..19bc868 100644
--- a/src/com/android/launcher3/widget/WidgetsRowViewHolder.java
+++ b/src/com/android/launcher3/widget/WidgetsRowViewHolder.java
@@ -16,10 +16,7 @@
 package com.android.launcher3.widget;
 
 import android.support.v7.widget.RecyclerView.ViewHolder;
-import android.view.View;
 import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
 
 public class WidgetsRowViewHolder extends ViewHolder {