Merge "Revert "Specifies descendantFocusability to Folder view."" into ub-launcher3-master
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 1fb8e8d..ef210d4 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -51,11 +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.GET_ACCOUNTS" />
     <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" />
@@ -181,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"
@@ -204,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/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/WallpaperCropActivity.java b/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
index f2bb509..eb47380 100644
--- a/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
+++ b/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
@@ -19,10 +19,7 @@
 import android.annotation.TargetApi;
 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.res.Resources;
 import android.graphics.Bitmap;
@@ -58,9 +55,6 @@
 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
@@ -69,7 +63,6 @@
      * 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;
 
@@ -223,14 +216,12 @@
 
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
     protected boolean isActivityDestroyed() {
-        return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
-                && isDestroyed();
+        return Utilities.ATLEAST_JB_MR1 && isDestroyed();
     }
 
     @Thunk void addReusableBitmap(TileSource src) {
         synchronized (mReusableBitmaps) {
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
-                    && src instanceof BitmapRegionTileSource) {
+            if (Utilities.ATLEAST_KITKAT && src instanceof BitmapRegionTileSource) {
                 Bitmap preview = ((BitmapRegionTileSource) src).getBitmap();
                 if (preview != null && preview.isMutable()) {
                     mReusableBitmaps.add(preview);
@@ -304,7 +295,7 @@
         final Point bounds = cropTask.getImageBounds();
         Runnable onEndCrop = new Runnable() {
             public void run() {
-                updateWallpaperDimensions(bounds.x, bounds.y);
+                WallpaperUtils.saveWallpaperDimensions(bounds.x, bounds.y, WallpaperCropActivity.this);
                 if (finishActivityWhenDone) {
                     setResult(Activity.RESULT_OK);
                     finish();
@@ -320,7 +311,7 @@
             Resources res, int resId, final boolean finishActivityWhenDone) {
         // 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());
@@ -330,7 +321,7 @@
             public void run() {
                 // Passing 0, 0 will cause launcher to revert to using the
                 // default wallpaper size
-                updateWallpaperDimensions(0, 0);
+                WallpaperUtils.saveWallpaperDimensions(0, 0, WallpaperCropActivity.this);
                 if (finishActivityWhenDone) {
                     setResult(Activity.RESULT_OK);
                     finish();
@@ -422,7 +413,7 @@
 
         Runnable onEndCrop = new Runnable() {
             public void run() {
-                updateWallpaperDimensions(outWidth, outHeight);
+                WallpaperUtils.saveWallpaperDimensions(outWidth, outHeight, WallpaperCropActivity.this);
                 if (finishActivityWhenDone) {
                     setResult(Activity.RESULT_OK);
                     finish();
@@ -437,22 +428,6 @@
         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);
-        }
-        editor.commit();
-        WallpaperUtils.suggestWallpaperDimension(getResources(),
-                sp, getWindowManager(), WallpaperManager.getInstance(getContext()), true);
-    }
-
     static class LoadRequest {
         BitmapSource src;
         boolean touchEnabled;
diff --git a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
index 88dc3e2..e2c008b 100644
--- a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
+++ b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
@@ -16,7 +16,7 @@
 
 package com.android.launcher3;
 
-import android.Manifest.permission;
+import android.Manifest;
 import android.animation.LayoutTransition;
 import android.annotation.TargetApi;
 import android.app.ActionBar;
@@ -38,7 +38,6 @@
 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;
@@ -100,6 +99,7 @@
 
     @Thunk LinearLayout mWallpapersView;
     @Thunk HorizontalScrollView mWallpaperScrollContainer;
+    @Thunk View mWallpaperStrip;
 
     @Thunk ActionMode.Callback mActionModeCallback;
     @Thunk ActionMode mActionMode;
@@ -200,8 +200,8 @@
         @Override
         public void onClick(final WallpaperPickerActivity a) {
             a.setWallpaperButtonEnabled(false);
-            final BitmapRegionTileSource.UriBitmapSource bitmapSource =
-                    new BitmapRegionTileSource.UriBitmapSource(a.getContext(), Uri.fromFile(mFile));
+            final BitmapRegionTileSource.FilePathBitmapSource bitmapSource =
+                    new BitmapRegionTileSource.FilePathBitmapSource(mFile.getAbsolutePath());
             a.setCropViewTileSource(bitmapSource, false, true, null, new Runnable() {
 
                 @Override
@@ -239,7 +239,7 @@
         public void onClick(final WallpaperPickerActivity a) {
             a.setWallpaperButtonEnabled(false);
             final BitmapRegionTileSource.ResourceBitmapSource bitmapSource =
-                    new BitmapRegionTileSource.ResourceBitmapSource(mResources, mResId);
+                    new BitmapRegionTileSource.ResourceBitmapSource(mResources, mResId, a);
             a.setCropViewTileSource(bitmapSource, false, false, new CropViewScaleProvider() {
 
                 @Override
@@ -379,6 +379,7 @@
 
         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
@@ -386,15 +387,15 @@
                 if (mAnim != null) {
                     mAnim.cancel();
                 }
-                if (mWallpaperScrollContainer.getAlpha() == 1f) {
+                if (mWallpaperStrip.getAlpha() == 1f) {
                     mIgnoreNextTap = true;
                 }
-                mAnim = mWallpaperScrollContainer.animate();
+                mAnim = mWallpaperStrip.animate();
                 mAnim.alpha(0f)
                     .setDuration(150)
                     .withEndAction(new Runnable() {
                         public void run() {
-                            mWallpaperScrollContainer.setVisibility(View.INVISIBLE);
+                            mWallpaperStrip.setVisibility(View.INVISIBLE);
                         }
                     });
                 mAnim.setInterpolator(new AccelerateInterpolator(0.75f));
@@ -412,8 +413,8 @@
                     if (mAnim != null) {
                         mAnim.cancel();
                     }
-                    mWallpaperScrollContainer.setVisibility(View.VISIBLE);
-                    mAnim = mWallpaperScrollContainer.animate();
+                    mWallpaperStrip.setVisibility(View.VISIBLE);
+                    mAnim = mWallpaperStrip.animate();
                     mAnim.alpha(1f)
                          .setDuration(150)
                          .setInterpolator(new DecelerateInterpolator(0.75f));
@@ -548,7 +549,12 @@
                 new View.OnClickListener() {
                     @Override
                     public void onClick(View v) {
-                        if (mSelectedTile != null) {
+                        // Ensure that a tile is slelected and loaded.
+                        if (mSelectedTile != null && mCropView.getTileSource() != null) {
+                            // Prevent user from selecting any new tile.
+                            mWallpaperStrip.setVisibility(View.GONE);
+                            actionBar.hide();
+
                             WallpaperTileInfo info = (WallpaperTileInfo) mSelectedTile.getTag();
                             info.onSave(WallpaperPickerActivity.this);
                         } else {
@@ -713,10 +719,10 @@
 
     public void onStop() {
         super.onStop();
-        mWallpaperScrollContainer = (HorizontalScrollView) findViewById(R.id.wallpaper_scroll_container);
-        if (mWallpaperScrollContainer.getAlpha() < 1f) {
-            mWallpaperScrollContainer.setAlpha(1f);
-            mWallpaperScrollContainer.setVisibility(View.VISIBLE);
+        mWallpaperStrip = findViewById(R.id.wallpaper_strip);
+        if (mWallpaperStrip.getAlpha() < 1f) {
+            mWallpaperStrip.setAlpha(1f);
+            mWallpaperStrip.setVisibility(View.VISIBLE);
         }
     }
 
@@ -970,10 +976,8 @@
 
         if (partner == null || !partner.hideDefaultWallpaper()) {
             // Add an entry for the default wallpaper (stored in system resources)
-            WallpaperTileInfo defaultWallpaperInfo =
-                    (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
-                    ? getPreKKDefaultWallpaperInfo()
-                    : getDefaultWallpaper();
+            WallpaperTileInfo defaultWallpaperInfo = Utilities.ATLEAST_KITKAT
+                    ? getDefaultWallpaper() : getPreKKDefaultWallpaperInfo();
             if (defaultWallpaperInfo != null) {
                 bundled.add(0, defaultWallpaperInfo);
             }
@@ -1026,7 +1030,7 @@
         } else {
             Resources res = getResources();
             Point defaultThumbSize = getDefaultThumbnailSize(res);
-            int rotation = BitmapUtils.getRotationFromExif(res, resId);
+            int rotation = BitmapUtils.getRotationFromExif(res, resId, this);
             thumb = createThumbnail(
                     defaultThumbSize, getContext(), null, null, sysRes, resId, rotation, false);
             if (thumb != null) {
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 a8e2b60..5f393fc 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -3,10 +3,10 @@
 }
 
 -keep class com.android.launcher3.BaseRecyclerViewFastScrollBar {
-  public void setWidth(int);
-  public int getWidth();
-  public void setTrackAlpha(int);
-  public int getTrackAlpha();
+  public void setThumbWidth(int);
+  public int getThumbWidth();
+  public void setTrackWidth(int);
+  public int getTrackWidth();
 }
 
 -keep class com.android.launcher3.BaseRecyclerViewFastScrollPopup {
@@ -39,7 +39,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/protos/backup.proto b/protos/backup.proto
index d8d94e8..6704e08 100644
--- a/protos/backup.proto
+++ b/protos/backup.proto
@@ -101,7 +101,10 @@
   optional string iconPackage = 16;
   optional string iconResource = 17;
   optional bytes icon = 18;
+
+  // Added in backup version 4
   optional TargetType targetType = 19 [default = TARGET_NONE];
+  optional int32 rank = 20;
 }
 
 message Screen {
@@ -121,6 +124,7 @@
   optional Resource icon = 4;
   optional Resource preview = 5;
 
+  // Added in backup version 3
   // Assume that a widget is resizable upto 2x2 if no data is available
   optional int32 minSpanX = 6 [default = 2];
   optional int32 minSpanY = 7 [default = 2];
diff --git a/res/drawable-hdpi/ic_arrow_back_grey.png b/res/drawable-hdpi/ic_arrow_back_grey.png
index ccd3900..c7c0088 100755
--- a/res/drawable-hdpi/ic_arrow_back_grey.png
+++ b/res/drawable-hdpi/ic_arrow_back_grey.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_search_grey.png b/res/drawable-hdpi/ic_search_grey.png
index f4c5e27..bd20ba0 100755
--- a/res/drawable-hdpi/ic_search_grey.png
+++ b/res/drawable-hdpi/ic_search_grey.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_arrow_back_grey.png b/res/drawable-mdpi/ic_arrow_back_grey.png
index 11996ef..5892c77 100755
--- a/res/drawable-mdpi/ic_arrow_back_grey.png
+++ b/res/drawable-mdpi/ic_arrow_back_grey.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_search_grey.png b/res/drawable-mdpi/ic_search_grey.png
index e83891c..c386dbb 100755
--- a/res/drawable-mdpi/ic_search_grey.png
+++ b/res/drawable-mdpi/ic_search_grey.png
Binary files differ
diff --git a/res/drawable-v21/all_apps_search_market_bg.xml b/res/drawable-v21/all_apps_search_market_bg.xml
new file mode 100644
index 0000000..7bd2f88
--- /dev/null
+++ b/res/drawable-v21/all_apps_search_market_bg.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="@color/all_apps_search_market_button_focused_bg_color">
+    <item android:drawable="@color/quantum_panel_bg_color" />
+</ripple>
diff --git a/res/drawable-xhdpi/ic_arrow_back_grey.png b/res/drawable-xhdpi/ic_arrow_back_grey.png
index 79b9b48..11996ef 100755
--- a/res/drawable-xhdpi/ic_arrow_back_grey.png
+++ b/res/drawable-xhdpi/ic_arrow_back_grey.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_search_grey.png b/res/drawable-xhdpi/ic_search_grey.png
index bd5fdf4..e83891c 100755
--- a/res/drawable-xhdpi/ic_search_grey.png
+++ b/res/drawable-xhdpi/ic_search_grey.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_arrow_back_grey.png b/res/drawable-xxhdpi/ic_arrow_back_grey.png
index 8e42e09..ccd3900 100755
--- a/res/drawable-xxhdpi/ic_arrow_back_grey.png
+++ b/res/drawable-xxhdpi/ic_arrow_back_grey.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_search_grey.png b/res/drawable-xxhdpi/ic_search_grey.png
index 1d5c913..f4c5e27 100755
--- a/res/drawable-xxhdpi/ic_search_grey.png
+++ b/res/drawable-xxhdpi/ic_search_grey.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_arrow_back_grey.png b/res/drawable-xxxhdpi/ic_arrow_back_grey.png
index 854a9bd..79b9b48 100755
--- a/res/drawable-xxxhdpi/ic_arrow_back_grey.png
+++ b/res/drawable-xxxhdpi/ic_arrow_back_grey.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_search_grey.png b/res/drawable-xxxhdpi/ic_search_grey.png
index 28519fd..bd5fdf4 100755
--- a/res/drawable-xxxhdpi/ic_search_grey.png
+++ b/res/drawable-xxxhdpi/ic_search_grey.png
Binary files differ
diff --git a/res/drawable/all_apps_search_market_bg.xml b/res/drawable/all_apps_search_market_bg.xml
new file mode 100644
index 0000000..5278e00
--- /dev/null
+++ b/res/drawable/all_apps_search_market_bg.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true" android:drawable="@color/all_apps_search_market_button_focused_bg_color" />
+    <item android:state_pressed="true" android:drawable="@color/all_apps_search_market_button_focused_bg_color" />
+    <item android:drawable="@android:color/transparent" />
+</selector>
diff --git a/res/drawable/horizontal_line.xml b/res/drawable/horizontal_line.xml
new file mode 100644
index 0000000..3f3f17e3
--- /dev/null
+++ b/res/drawable/horizontal_line.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <size android:height="1dp" />
+    <solid android:color="#ddd" />
+</shape>
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/all_apps_empty_search.xml b/res/layout/all_apps_empty_search.xml
index f60c4a0..b9b493e 100644
--- a/res/layout/all_apps_empty_search.xml
+++ b/res/layout/all_apps_empty_search.xml
@@ -18,11 +18,14 @@
     android:id="@+id/empty_text"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:gravity="center"
-    android:paddingTop="24dp"
-    android:paddingBottom="24dp"
-    android:paddingRight="@dimen/all_apps_grid_view_start_margin"
-    android:textSize="16sp"
-    android:textColor="#4c4c4c"
+    android:gravity="start"
+    android:paddingTop="20dp"
+    android:paddingBottom="8dp"
+    android:paddingLeft="16dp"
+    android:paddingRight="16dp"
+    android:fontFamily="sans-serif-medium"
+    android:textSize="14sp"
+    android:textColor="#212121"
+    android:alpha="0.56"
     android:focusable="false" />
 
diff --git a/res/layout/all_apps_search_bar.xml b/res/layout/all_apps_search_bar.xml
index cf30eac..69a66c8 100644
--- a/res/layout/all_apps_search_bar.xml
+++ b/res/layout/all_apps_search_bar.xml
@@ -32,14 +32,13 @@
             android:id="@+id/dismiss_search_button"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_marginLeft="4dp"
-            android:layout_marginStart="4dp"
+            android:layout_gravity="center_vertical"
+            android:layout_marginLeft="16dp"
+            android:layout_marginStart="16dp"
             android:contentDescription="@string/all_apps_button_label"
-            android:paddingBottom="13dp"
-            android:paddingTop="13dp"
             android:src="@drawable/ic_arrow_back_grey" />
 
-        <com.android.launcher3.allapps.AllAppsSearchEditView
+        <com.android.launcher3.ExtendedEditText
             android:id="@+id/search_box_input"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
@@ -48,7 +47,7 @@
             android:gravity="fill_horizontal|center_vertical"
             android:hint="@string/all_apps_search_bar_hint"
             android:inputType="text|textNoSuggestions|textCapWords"
-            android:imeOptions="actionDone|flagNoExtractUi"
+            android:imeOptions="actionSearch|flagNoExtractUi"
             android:maxLines="1"
             android:paddingLeft="8dp"
             android:scrollHorizontally="true"
@@ -63,10 +62,8 @@
         android:layout_width="wrap_content"
         android:layout_height="@dimen/all_apps_search_bar_height"
         android:layout_gravity="end|center_vertical"
-        android:layout_marginEnd="4dp"
-        android:layout_marginRight="4dp"
+        android:layout_marginEnd="16dp"
+        android:layout_marginRight="16dp"
         android:contentDescription="@string/all_apps_search_bar_hint"
-        android:paddingBottom="13dp"
-        android:paddingTop="13dp"
         android:src="@drawable/ic_search_grey" />
 </FrameLayout>
\ No newline at end of file
diff --git a/res/layout/all_apps_search_market.xml b/res/layout/all_apps_search_market.xml
new file mode 100644
index 0000000..1282069
--- /dev/null
+++ b/res/layout/all_apps_search_market.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/search_market_text"
+    android:layout_width="wrap_content"
+    android:layout_height="48dp"
+    android:gravity="start|center_vertical"
+    android:paddingLeft="16dp"
+    android:paddingRight="16dp"
+    android:fontFamily="sans-serif-medium"
+    android:textSize="14sp"
+    android:textColor="#009688"
+    android:textAllCaps="true"
+    android:focusable="false"
+    android:background="@drawable/all_apps_search_market_bg" />
diff --git a/res/layout/all_apps_search_market_divider.xml b/res/layout/all_apps_search_market_divider.xml
new file mode 100644
index 0000000..3909781
--- /dev/null
+++ b/res/layout/all_apps_search_market_divider.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+<ImageView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center"
+    android:paddingTop="16dp"
+    android:paddingBottom="8dp"
+    android:paddingLeft="16dp"
+    android:paddingRight="16dp"
+    android:focusable="false"
+    android:scaleType="matrix"
+    android:src="@drawable/horizontal_line" />
\ No newline at end of file
diff --git a/res/layout/user_folder.xml b/res/layout/user_folder.xml
index ecf7def..275840d 100644
--- a/res/layout/user_folder.xml
+++ b/res/layout/user_folder.xml
@@ -53,7 +53,7 @@
         android:paddingLeft="8dp"
         android:paddingRight="8dp" >
 
-        <com.android.launcher3.FolderEditText
+        <com.android.launcher3.ExtendedEditText
             android:id="@+id/folder_name"
             android:layout_width="0dp"
             android:layout_height="wrap_content"
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 7004524..78be3f4 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Deursoek programme"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Laai tans programme …"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Geen programme gevind wat met \"<xliff:g id="QUERY">%1$s</xliff:g>\" ooreenstem nie"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Gaan na <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Niks meer spasie op die tuisskerm nie."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Geen plek meer in die Gunstelinge-laai nie"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Programme"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index d7fed9d..b5d982e 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"መተግበሪያዎችን ይፈልጉ"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"መተግበሪያዎችን በመጫን ላይ..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"ከ«<xliff:g id="QUERY">%1$s</xliff:g>» ጋር የሚዛመዱ ምንም መተግበሪያዎች አልተገኙም"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"ወደ <xliff:g id="QUERY">%1$s</xliff:g> ሂድ"</string>
     <string name="out_of_space" msgid="4691004494942118364">"በዚህ መነሻ ማያ ገጽ ላይ ምንም ቦታ የለም።"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"በተወዳጆች መሣቢያ ውስጥ ተጨማሪ ቦታ የለም"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"መተግበሪያዎች"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index b7cd901..619e11d 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -32,12 +32,13 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"البحث في التطبيقات"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"جارٍ تحميل التطبيقات…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"لم يتم العثور على أية تطبيقات تتطابق مع \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"الانتقال إلى <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"ليس هناك مساحة أخرى في هذه الشاشة الرئيسية."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"لا يوجد المزيد من الحقول في علبة المفضلة"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"التطبيقات"</string>
     <string name="all_apps_home_button_label" msgid="252062713717058851">"الرئيسية"</string>
     <string name="delete_target_label" msgid="1822697352535677073">"إزالة"</string>
-    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"إزالة"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"إلغاء التثبيت"</string>
     <string name="info_target_label" msgid="8053346143994679532">"معلومات عن التطبيق"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"تثبيت اختصارات"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"للسماح لتطبيق ما بإضافة اختصارات بدون تدخل المستخدم."</string>
diff --git a/res/values-az-rAZ/strings.xml b/res/values-az-rAZ/strings.xml
index 099a220..b9ea83f 100644
--- a/res/values-az-rAZ/strings.xml
+++ b/res/values-az-rAZ/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Tətbiq Axtarın"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Tətbiqlər endirilir..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" sorğusuna uyğun Tətbiqlər tapılmadı"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> daxil olun"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Bu Əsas ekranda boş yer yoxdur."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Favoritlər-də yer yoxdur"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Tətbiqlər"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index eeaa1ed..214c64b 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Търсене в приложенията"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Приложенията се зареждат…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Няма намерени приложения, съответстващи на „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Отваряне на <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"На този начален екран няма повече място."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Няма повече място в областта с любимите"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Приложения"</string>
diff --git a/res/values-bn-rBD/strings.xml b/res/values-bn-rBD/strings.xml
index 57f7b51..129d250 100644
--- a/res/values-bn-rBD/strings.xml
+++ b/res/values-bn-rBD/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"অ্যাপ্লিকেশানগুলি অনুসন্ধান করুন"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"অ্যাপ্লিকেশানগুলি লোড হচ্ছে..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" এর সাথে মেলে এমন কোনো অ্যাপ্লিকেশান পাওয়া যায়নি"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> এ যান"</string>
     <string name="out_of_space" msgid="4691004494942118364">"এই হোম স্ক্রীনে আর কোনো জায়গা নেই৷"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"পছন্দসই ট্রে-তে আর কোনো জায়গা নেই"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"অ্যাপ্লিকেশানগুলি"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 83f284a..cf874a4 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Cerca a les aplicacions"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"S\'estan carregant les aplicacions..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"No s\'ha trobat cap aplicació que coincideixi amb <xliff:g id="QUERY">%1$s</xliff:g>"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Vés a <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Ja no queda espai en aquesta pantalla d\'inici."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"No hi ha més espai a la safata Preferits."</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Aplicacions"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index c99a7ed..c2f9f42 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Hledat aplikace"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Načítání aplikací…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Dotazu „<xliff:g id="QUERY">%1$s</xliff:g>“ neodpovídají žádné aplikace"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Přejít na <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Na této ploše již není místo."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Na panelu Oblíbené položky již není místo."</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Aplikace"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index eeab92e..a948705 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Søg i Apps"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Indlæser apps…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Der blev ikke fundet nogen apps, som matcher \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Gå til <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Der er ikke mere plads på denne startskærm."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Der er ikke mere plads i bakken Foretrukne"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index ac6f76b..96ae74b 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"In Apps suchen"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Apps werden geladen..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Keine Apps für \"<xliff:g id="QUERY">%1$s</xliff:g>\" gefunden"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Gehe zu <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Auf diesem Startbildschirm ist kein Platz mehr vorhanden."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Ablage \"Favoriten\" ist voll."</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index a60458a..81b43e2 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Αναζήτηση εφαρμογών"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Φόρτωση εφαρμογών…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Δεν βρέθηκαν εφαρμογές για το ερώτημα \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Μετάβαση σε <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Δεν υπάρχει χώρος σε αυτήν την αρχική οθόνη."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Δεν υπάρχει επιπλέον χώρος στην περιοχή Αγαπημένα"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Εφαρμογές"</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 09963e4..f2a6eab 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Search Apps"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Loading Apps…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"No Apps found matching \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Go to <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"No more room on this Home screen."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"No more room in the Favourites tray"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 09963e4..f2a6eab 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Search Apps"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Loading Apps…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"No Apps found matching \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Go to <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"No more room on this Home screen."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"No more room in the Favourites tray"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 09963e4..f2a6eab 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Search Apps"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Loading Apps…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"No Apps found matching \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Go to <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"No more room on this Home screen."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"No more room in the Favourites tray"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 6366742..c754493 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Buscar aplicaciones"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Cargando aplicaciones…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"No hay aplicaciones que coincidan con <xliff:g id="QUERY">%1$s</xliff:g>."</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Ir a <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"No hay más espacio en esta pantalla principal."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"La bandeja de favoritos está llena."</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Aplicaciones"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 34331fc..16cd84f 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Busca aplicaciones"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Cargando aplicaciones…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"No se han encontrado aplicaciones que contengan \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Ir a <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"No queda espacio en la pantalla de inicio."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"La bandeja de favoritos está completa"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Aplicaciones"</string>
diff --git a/res/values-et-rEE/strings.xml b/res/values-et-rEE/strings.xml
index c4ba823..84ca45a 100644
--- a/res/values-et-rEE/strings.xml
+++ b/res/values-et-rEE/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Otsige rakendustest"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Rakenduste laadimine ..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Päringule „<xliff:g id="QUERY">%1$s</xliff:g>” ei vastanud ükski rakendus"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Mine: <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Sellel avaekraanil pole enam ruumi."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Salves Lemmikud pole rohkem ruumi"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Rakendused"</string>
diff --git a/res/values-eu-rES/strings.xml b/res/values-eu-rES/strings.xml
index 8bfca6c..096a4f9 100644
--- a/res/values-eu-rES/strings.xml
+++ b/res/values-eu-rES/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Bilatu aplikazioetan"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Aplikazioak kargatzen…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Ez da aurkitu \"<xliff:g id="QUERY">%1$s</xliff:g>\" bilaketarekin bat datorren aplikaziorik"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Joan hona: <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Hasierako pantaila honetan ez dago toki gehiago."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Ez dago toki gehiago Gogokoak erretiluan"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Aplikazioak"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 701d59d..3b6d01a 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"جستجوی برنامه‌ها"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"در حال بارگیری برنامه‌ها..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"هیچ برنامه‌ای مطابق با «<xliff:g id="QUERY">%1$s</xliff:g>» پیدا نشد"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"رفتن به <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"فضای بیشتری در این صفحه اصلی موجود نیست."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"فضای بیشتری در سینی موارد دلخواه وجود ندارد"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"برنامه‌ها"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index f0766d6..47b580e 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Sovellushaku"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Ladataan sovelluksia…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"”<xliff:g id="QUERY">%1$s</xliff:g>” ei palauttanut sovelluksia."</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Siirry: <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Tässä aloitusruudussa ei ole enää tilaa."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Suosikit-valikossa ei ole enää tilaa"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Sovellukset"</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 0c19ef2..91271f7 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Rechercher des applications"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Chargement des applications en cours..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Aucune application trouvée correspondant à « <xliff:g id="QUERY">%1$s</xliff:g> »"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Aller à « <xliff:g id="QUERY">%1$s</xliff:g> »"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Pas d\'espace libre sur l\'écran d\'accueil."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Il n\'y a plus d\'espace dans la zone des favoris"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Applications"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 662bcf1..84e1594 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Rechercher dans les applications"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Chargement des applications en cours…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Aucune application ne correspond à la requête \"<xliff:g id="QUERY">%1$s</xliff:g>\"."</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Accéder à <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Pas d\'espace libre sur cet écran d\'accueil."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Plus d\'espace disponible dans la zone de favoris."</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Applications"</string>
diff --git a/res/values-gl-rES/strings.xml b/res/values-gl-rES/strings.xml
index 6cf76a7..ccfe21e 100644
--- a/res/values-gl-rES/strings.xml
+++ b/res/values-gl-rES/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Aplicacións de busca"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Cargando aplicacións..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Non se atoparon aplicacións que coincidan con \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Ir a <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Non hai máis espazo nesta pantalla de inicio."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Non hai máis espazo na bandexa de favoritos"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Aplicacións"</string>
diff --git a/res/values-gu-rIN/strings.xml b/res/values-gu-rIN/strings.xml
index 4a2274f..db056a0 100644
--- a/res/values-gu-rIN/strings.xml
+++ b/res/values-gu-rIN/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"શોધ એપ્લિકેશનો"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"એપ્લિકેશનો લોડ કરી રહ્યું છે…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" થી મેળ ખાતી કોઈ એપ્લિકેશનો મળી નથી"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> પર જાઓ"</string>
     <string name="out_of_space" msgid="4691004494942118364">"આ હોમ સ્ક્રીન પર વધુ જગ્યા નથી."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"મનપસંદ ટ્રે પર વધુ જગ્યા નથી"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"એપ્લિકેશનો"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 6fdbf24..7cf4f33 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ऐप्‍स खोजें"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"ऐप्स लोड हो रहे हैं..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" से मिलान करने वाला कोई ऐप नहीं मिला"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> पर जाएं"</string>
     <string name="out_of_space" msgid="4691004494942118364">"इस होम स्‍क्रीन पर स्थान शेष नहीं है."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"पसंदीदा ट्रे में और स्थान नहीं है"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"ऐप्लिकेशन"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index a54a7e4..2679570 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Pretraži aplikacije"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Učitavanje aplikacija…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Nema aplikacija podudarnih s upitom \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Idite na <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Na ovom početnom zaslonu više nema mjesta."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Nema više prostora na traci Favoriti"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Aplikacije"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index e9fc574..41c38b2 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Alkalmazások keresése"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Alkalmazások betöltése…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Egy alkalmazás sem található a(z) „<xliff:g id="QUERY">%1$s</xliff:g>” lekérdezésre."</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Keresse fel ezt: <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Nincs több hely ezen a kezdőképernyőn."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Nincs több hely a Kedvencek tálcán"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Alkalmazások"</string>
diff --git a/res/values-hy-rAM/strings.xml b/res/values-hy-rAM/strings.xml
index de4aed1..49a9d18 100644
--- a/res/values-hy-rAM/strings.xml
+++ b/res/values-hy-rAM/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Հավելվածների որոնում"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Հավելվածների բեռնում…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"«<xliff:g id="QUERY">%1$s</xliff:g>» հարցմանը համապատասխանող հավելվածներ չեն գտնվել"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Գնալ <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Այլևս տեղ չկա այս հիմնական էկրանին:"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Ընտրյալների ցուցակում այլևս ազատ տեղ չկա"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Ծրագրեր"</string>
@@ -59,11 +60,11 @@
     <string name="workspace_cling_longpress_title" msgid="9173998993909018310">"Պաստառներ, վիջեթներ և կարգավորումներ"</string>
     <string name="workspace_cling_longpress_description" msgid="4119994475505235248">"Հարմարեցնելու համար հպեք և պահեք հետնաշերտի վրա"</string>
     <string name="workspace_cling_longpress_dismiss" msgid="368660286867640874">"ՀԱՍԿԱՆԱԼԻ Է"</string>
-    <string name="folder_opened" msgid="94695026776264709">"Թղթապանակը բաց է, <xliff:g id="WIDTH">%1$d</xliff:g>-ից <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Պանակը բաց է, <xliff:g id="WIDTH">%1$d</xliff:g>-ից <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="1884479294466410023">"Հպեք՝ պանակը փակելու համար"</string>
     <string name="folder_tap_to_rename" msgid="9191075570492871147">"Հպեք՝ վերանվանումը պահելու համար"</string>
-    <string name="folder_closed" msgid="4100806530910930934">"Թղթապանակը փակ է"</string>
-    <string name="folder_renamed" msgid="1794088362165669656">"Թղթապանակը վերանվանվեց <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Պանակը փակ է"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Պանակը վերանվանվեց <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format" msgid="6629239338071103179">"Թղթապանակ՝ <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="widget_button_text" msgid="2880537293434387943">"Վիջեթներ"</string>
     <string name="wallpaper_button_text" msgid="8404103075899945851">"Պաստառներ"</string>
@@ -87,7 +88,7 @@
     <string name="add_to_folder_with_app" msgid="4534929978967147231">"Ավելացնել «<xliff:g id="NAME">%1$s</xliff:g>» պանակին"</string>
     <string name="added_to_folder" msgid="4793259502305558003">"Տարրն ավելացվեց թղթապանակում"</string>
     <string name="create_folder_with" msgid="4050141361160214248">"Ստեղծել թղթապանակ, օգտագործելով՝ <xliff:g id="NAME">%1$s</xliff:g>"</string>
-    <string name="folder_created" msgid="6409794597405184510">"Թղթապանակը ստեղծվեց"</string>
+    <string name="folder_created" msgid="6409794597405184510">"Պանակը ստեղծվեց"</string>
     <string name="action_move_to_workspace" msgid="1603837886334246317">"Տեղափոխել Հիմնական էկրան"</string>
     <string name="action_move_screen_left" msgid="8854216831569401665">"Տեղափոխել էկրանը ձախ"</string>
     <string name="action_move_screen_right" msgid="329334910274311123">"Տեղափոխել էկրանը աջ"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index c65254a..21c2080 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Telusuri Apps"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Memuat Aplikasi..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Tidak ditemukan Aplikasi yang cocok dengan \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Buka <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Tidak ada ruang lagi pada layar Utama ini."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Tidak ada ruang tersisa di baki Favorit"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Aplikasi"</string>
diff --git a/res/values-is-rIS/strings.xml b/res/values-is-rIS/strings.xml
index 1d46cdc..7422eaf 100644
--- a/res/values-is-rIS/strings.xml
+++ b/res/values-is-rIS/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Leita í forritum"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Hleður forrit…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Ekki fundust forrit sem samsvara „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Fara í <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Ekki meira pláss á þessum heimaskjá."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Ekki meira pláss í bakka fyrir uppáhald"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Forrit"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 02057a1..e1e75b3 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Cerca app"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Caricamento di app…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Nessuna app trovata corrispondente a \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Vai a <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Spazio nella schermata Home esaurito."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Spazio esaurito nella barra dei Preferiti"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"App"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index a8039f7..3686abd 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"חפש אפליקציות"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"טוען אפליקציות…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"לא נמצאו אפליקציות התואמות ל-\"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"עבור אל <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"אין עוד מקום במסך דף הבית הזה."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"אין עוד מקום במגש המועדפים"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"אפליקציות"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index ba58d88..87b3fa4 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"アプリを検索"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"アプリを読み込んでいます…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"「<xliff:g id="QUERY">%1$s</xliff:g>」に一致するアプリは見つかりませんでした"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g>にアクセス"</string>
     <string name="out_of_space" msgid="4691004494942118364">"このホーム画面に空きスペースがありません。"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"お気に入りトレイに空きスペースがありません"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"アプリ"</string>
@@ -57,7 +58,7 @@
     <string name="migration_cling_copy_apps" msgid="946331230090919440">"アイコンをコピー"</string>
     <string name="migration_cling_use_default" msgid="2626475813981258626">"初期状態にリセットする"</string>
     <string name="workspace_cling_longpress_title" msgid="9173998993909018310">"壁紙、ウィジェット、設定"</string>
-    <string name="workspace_cling_longpress_description" msgid="4119994475505235248">"カスタマイズするにはバックグラウンドを押し続けます"</string>
+    <string name="workspace_cling_longpress_description" msgid="4119994475505235248">"カスタマイズするには背景を押し続けます"</string>
     <string name="workspace_cling_longpress_dismiss" msgid="368660286867640874">"OK"</string>
     <string name="folder_opened" msgid="94695026776264709">"フォルダが開いています。<xliff:g id="WIDTH">%1$d</xliff:g>x<xliff:g id="HEIGHT">%2$d</xliff:g>の大きさです"</string>
     <string name="folder_tap_to_close" msgid="1884479294466410023">"タップしてフォルダを閉じます"</string>
diff --git a/res/values-ka-rGE/strings.xml b/res/values-ka-rGE/strings.xml
index 07030d8..2900330 100644
--- a/res/values-ka-rGE/strings.xml
+++ b/res/values-ka-rGE/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"აპების ძიება"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"აპები იტვირთება..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"„<xliff:g id="QUERY">%1$s</xliff:g>“-ის თანხვედრი აპები არ მოიძებნა"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"გადადი <xliff:g id="QUERY">%1$s</xliff:g>-ში"</string>
     <string name="out_of_space" msgid="4691004494942118364">"ამ მთავარ ეკრანზე ადგილი აღარ არის."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"რჩეულების თაროზე ადგილი არ არის"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"აპები"</string>
diff --git a/res/values-kk-rKZ/strings.xml b/res/values-kk-rKZ/strings.xml
index e79c9ce..cae5e5e 100644
--- a/res/values-kk-rKZ/strings.xml
+++ b/res/values-kk-rKZ/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Қолданбаларды іздеу"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Қолданбалар жүктелуде…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"«<xliff:g id="QUERY">%1$s</xliff:g>» сұрауына сәйкес келетін қолданбалар жоқ"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> сұрауына өту"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Бұл Негізгі экранда орын қалмады."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Қалаулылар науасында орын қалмады"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Қолданбалар"</string>
diff --git a/res/values-km-rKH/strings.xml b/res/values-km-rKH/strings.xml
index 9f7f537..5ffbf76 100644
--- a/res/values-km-rKH/strings.xml
+++ b/res/values-km-rKH/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ស្វែងរកកម្មវិធី"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"កំពុងដំណើរការកម្មវិធី..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"គ្មានកម្មវិធីដែលត្រូវជាមួយ \"<xliff:g id="QUERY">%1$s</xliff:g>\" ទេ"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"ចូលទៅកាន់ <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"គ្មាន​បន្ទប់​នៅ​លើ​អេក្រង់​ដើម​នេះ​ទៀត​ទេ។"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"គ្មាន​បន្ទប់​​ក្នុង​ថាស​និយម​ប្រើ"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"កម្មវិធី"</string>
diff --git a/res/values-kn-rIN/strings.xml b/res/values-kn-rIN/strings.xml
index 5cd8792..a1aab7b 100644
--- a/res/values-kn-rIN/strings.xml
+++ b/res/values-kn-rIN/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ಅಪ್ಲಿಕೇಷನ್‌ಗಳನ್ನು ಹುಡುಕಿ"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಲೋಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" ಹೊಂದಿಕೆಯ ಯಾವುದೇ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಕಂಡುಬಂದಿಲ್ಲ"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> ಗೆ ಹೋಗಿ"</string>
     <string name="out_of_space" msgid="4691004494942118364">"ಈ ಮುಖಪುಟದ ಪರದೆಯಲ್ಲಿ ಹೆಚ್ಚು ಸ್ಥಳಾವಕಾಶವಿಲ್ಲ."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"ಮೆಚ್ಚಿನವುಗಳ ಟ್ರೇನಲ್ಲಿ ಹೆಚ್ಚಿನ ಸ್ಥಳಾವಕಾಶವಿಲ್ಲ"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"ಅಪ್ಲಿಕೇಶನ್‌ಗಳು"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index fa445ac..e069712 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"앱 검색"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"앱 로드 중..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\'<xliff:g id="QUERY">%1$s</xliff:g>\'와(과) 일치하는 앱이 없습니다."</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g>(으)로 이동"</string>
     <string name="out_of_space" msgid="4691004494942118364">"홈 화면에 더 이상 공간이 없습니다."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"즐겨찾기 트레이에 더 이상 공간이 없습니다."</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"앱"</string>
diff --git a/res/values-ky-rKG/strings.xml b/res/values-ky-rKG/strings.xml
index 2078748..c530b5c 100644
--- a/res/values-ky-rKG/strings.xml
+++ b/res/values-ky-rKG/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Колдонмолорду издөө"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Колдонмолор жүктөлүүдө…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" дал келген колдонмолор табылган жок"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> сурамына өтүңүз"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Бул Үй экранында бош орун жок."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Тандамалдар тайпасында орун калган жок"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Колдонмолор"</string>
diff --git a/res/values-lo-rLA/strings.xml b/res/values-lo-rLA/strings.xml
index dff2612..005f026 100644
--- a/res/values-lo-rLA/strings.xml
+++ b/res/values-lo-rLA/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ຊອກຫາແອັບ"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"​ກຳ​ລັງ​ໂຫລດ​ແອັບ..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"ບໍ່​ພົບ​ແອັບ​ໃດ​ທີ່​ກົງ​ກັນ \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"ໄປ​ທີ່ <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"ບໍ່ມີຫ້ອງເຫຼືອໃນໜ້າຈໍຫຼັກນີ້."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"ບໍ່ມີບ່ອນຫວ່າງໃນຖາດສຳລັບເກັບສິ່ງທີ່ໃຊ້ເປັນປະຈຳ"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"ແອັບຯ"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index b5c0832..442874d 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Ieškoti programų"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Įkeliamos programos..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Nerasta jokių užklausą „<xliff:g id="QUERY">%1$s</xliff:g>“ atitinkančių programų"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Eiti į <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Šiame pagrindiniame ekrane vietos nebėra."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Mėgstamiausių dėkle nebėra vietos"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Programos"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 432bda4..1bef22e 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Meklēt lietotnes"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Notiek lietotņu ielāde…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Vaicājumam “<xliff:g id="QUERY">%1$s</xliff:g>” neatbilda neviena lietotne."</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Doties uz: <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Šajā sākuma ekrānā vairs nav vietas."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Izlases joslā vairs nav vietas."</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Lietotnes"</string>
diff --git a/res/values-mk-rMK/strings.xml b/res/values-mk-rMK/strings.xml
index 8c276e3..0a6704b 100644
--- a/res/values-mk-rMK/strings.xml
+++ b/res/values-mk-rMK/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Пребарување апликации"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Се вчитуваат апликации…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Не се најдени апликации што одговараат на „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Оди на <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Нема повеќе простор на овој екран на почетната страница."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Нема повеќе простор на лентата „Омилени“"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Апликации"</string>
diff --git a/res/values-ml-rIN/strings.xml b/res/values-ml-rIN/strings.xml
index 59ad153..2bd80d3 100644
--- a/res/values-ml-rIN/strings.xml
+++ b/res/values-ml-rIN/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ആപ്പ്‌സ് തിരയുക"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"ആപ്പ്‌സ് ലോഡുചെയ്യുന്നു..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" എന്നതുമായി പൊരുത്തപ്പെടുന്ന ആപ്പ്‌സൊന്നും കണ്ടെത്തിയില്ല"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> എന്നതിലേക്ക് പോവുക"</string>
     <string name="out_of_space" msgid="4691004494942118364">"ഈ ഹോം സ്‌ക്രീനിൽ ഒഴിവൊന്നുമില്ല."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"പ്രിയപ്പെട്ടവയുടെ ട്രേയിൽ ഒഴിവൊന്നുമില്ല"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"അപ്ലിക്കേഷനുകൾ"</string>
diff --git a/res/values-mn-rMN/strings.xml b/res/values-mn-rMN/strings.xml
index fafb37b..19ba66f 100644
--- a/res/values-mn-rMN/strings.xml
+++ b/res/values-mn-rMN/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Апп хайх"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Аппликейшныг ачаалж байна..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\"-тай нийцэх апп олдсонгүй"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> руу очих"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Энэ Нүүр дэлгэц зайгүй."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"\"Дуртай\" трей дээр өөр зай байхгүй байна"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Апп"</string>
diff --git a/res/values-mr-rIN/strings.xml b/res/values-mr-rIN/strings.xml
index 657010d..dc29b54 100644
--- a/res/values-mr-rIN/strings.xml
+++ b/res/values-mr-rIN/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"अॅप्स शोधा"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"अॅप्स लोड करीत आहे..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" शी जुळणारे कोणतेही अॅप्स आढळले नाहीत"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> वर जा"</string>
     <string name="out_of_space" msgid="4691004494942118364">"या मुख्य स्क्रीनवर आणखी जागा नाही."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"आवडीच्या ट्रे मध्ये आणखी जागा नाही"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"अॅप्स"</string>
diff --git a/res/values-ms-rMY/strings.xml b/res/values-ms-rMY/strings.xml
index 0c6a5f5..26f2748 100644
--- a/res/values-ms-rMY/strings.xml
+++ b/res/values-ms-rMY/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Cari Apl"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Memuatkan Apl…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Tiada Apl yang ditemui sepadan dengan \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Pergi ke <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Tiada lagi ruang pada skrin Laman Utama ini."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Tiada ruang dalam dulang Kegemaran lagi"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Apl"</string>
diff --git a/res/values-my-rMM/strings.xml b/res/values-my-rMM/strings.xml
index 19ec3a6..c13ff7e 100644
--- a/res/values-my-rMM/strings.xml
+++ b/res/values-my-rMM/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ရှာဖွေမှု Appများ"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"App များ ရယူနေစဉ်..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" နှင့်ကိုက်ညီသည့် အပ်ဖ်များမတွေ့ပါ"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g>ကို သွားပါ"</string>
     <string name="out_of_space" msgid="4691004494942118364">"ဤပင်မမျက်နှာစာတွင် နေရာလွတ် မကျန်တော့ပါ"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"အနှစ်သက်ဆုံးများ ထားရာတွင် နေရာလွတ် မကျန်တော့ပါ"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"အပ်ပလီကေးရှင်းများ"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 21b87c8..198d56c 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Søk i apper"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Laster inn apper …"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Fant ingen apper som samsvarer med «<xliff:g id="QUERY">%1$s</xliff:g>»"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Gå til <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Denne startsiden er full."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Favoritter-skuffen er full"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Apper"</string>
diff --git a/res/values-ne-rNP/strings.xml b/res/values-ne-rNP/strings.xml
index dd3b3f3..472bd4f 100644
--- a/res/values-ne-rNP/strings.xml
+++ b/res/values-ne-rNP/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"अनुप्रयोगहरू खोज्नुहोस्"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"अनुप्रयोगहरू लोड गरिँदै..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" सँग मिल्दो कुनै अनुप्रयोगहरू फेला परेनन्"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> मा जानुहोस्"</string>
     <string name="out_of_space" msgid="4691004494942118364">"यो गृह स्क्रिनमा कुनै थप ठाउँ छैन।"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"मनपर्ने ट्रे अब कुनै ठाँउ छैन"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"अनुप्रयोगहरू"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index fcbe375..6f49810 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Apps zoeken"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Apps laden…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Er zijn geen apps gevonden die overeenkomen met \'<xliff:g id="QUERY">%1$s</xliff:g>\'"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Ga naar <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Er is geen ruimte meer op dit startscherm."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Geen ruimte meer in het vak \'Favorieten\'"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
diff --git a/res/values-pa-rIN/strings.xml b/res/values-pa-rIN/strings.xml
index d510dc7..30c4428 100644
--- a/res/values-pa-rIN/strings.xml
+++ b/res/values-pa-rIN/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ਐਪਸ ਖੋਜੋ"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"ਐਪਸ ਲੋਡ ਕਰ ਰਿਹਾ ਹੈ..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" ਨਾਲ ਮਿਲਦੀ ਕੋਈ ਵੀ ਐਪਸ ਨਹੀਂ ਲੱਭੀਆਂ"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> ਤੇ ਜਾਓ"</string>
     <string name="out_of_space" msgid="4691004494942118364">"ਇਸ ਹੋਮ ਸਕ੍ਰੀਨ ਲਈ ਹੋਰ ਖਾਲੀ ਸਥਾਨ ਨਹੀਂ ਹੈ।"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"ਮਨਪਸੰਦ ਟ੍ਰੇ ਵਿੱਚ ਹੋਰ ਖਾਲੀ ਸਥਾਨ ਨਹੀਂ।"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"ਐਪਸ"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index b3b761d..a1fd29f 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Szukaj w aplikacjach"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Wczytuję aplikacje…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Nie znaleziono aplikacji pasujących do zapytania „<xliff:g id="QUERY">%1$s</xliff:g>”"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Otwórz <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Brak miejsca na tym ekranie głównym."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Brak miejsca w Ulubionych"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Aplikacje"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 9140b62..0d8a431 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Pesquisar aplicações"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"A carregar aplicações..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Não foram encontradas aplic. que correspondam a \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Aceder a <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Sem espaço suficiente neste Ecrã principal."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Não existe mais espaço no tabuleiro de Favoritos"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Aplicações"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index c8e6458..41e5c02 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Pesquisar apps"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Carregando apps…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Nenhum app encontrado que corresponda a \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Ir para <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Não há mais espaço na tela inicial."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Sem espaço na bandeja de favoritos"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 88cbc79..ffaca78 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Căutați aplicații"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Se încarcă aplicațiile..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Nu s-a găsit nicio aplicație pentru „<xliff:g id="QUERY">%1$s</xliff:g>”"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Accesați <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Nu mai este loc pe acest Ecran de pornire."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Spațiu epuizat în bara Preferate"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Aplicații"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 5b492fe..6e69e4d 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Поиск приложений"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Загрузка…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"По запросу \"<xliff:g id="QUERY">%1$s</xliff:g>\" ничего не найдено"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"На этом экране все занято"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"В разделе \"Избранное\" больше нет места"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Приложения"</string>
diff --git a/res/values-si-rLK/strings.xml b/res/values-si-rLK/strings.xml
index 68e3178..476fff8 100644
--- a/res/values-si-rLK/strings.xml
+++ b/res/values-si-rLK/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"යෙදුම් සෙවීම"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"යෙදුම් පූරණය වෙමින්…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" සමග ගැළපෙන යෙදුම් හමු නොවිණි"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> වෙත යන්න"</string>
     <string name="out_of_space" msgid="4691004494942118364">"මෙම මුල් පිටු තිරය මත තවත් අවසර නැත."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"ප්‍රියතම දෑ ඇති තැටියේ තවත් ඉඩ නොමැත"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"යෙදුම්"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 1cb76e3..703b46c 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Vyhľadávanie v aplikáciách"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Načítavajú sa aplikácie..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Nenašli sa žiadne aplikácie zodpovedajúce dopytu <xliff:g id="QUERY">%1$s</xliff:g>"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Prejsť na dopyt <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Na tejto ploche už nie je miesto"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Na paneli Obľúbené položky už nie je miesto"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Aplikácie"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 92c69a5..497378b 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Iskanje po aplikacijah"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Nalaganje aplikacij …"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Ni aplikacij, ki bi ustrezale poizvedbi »<xliff:g id="QUERY">%1$s</xliff:g>«"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Odpri storitev <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Na tem začetnem zaslonu ni več prostora."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"V vrstici za priljubljene ni več prostora"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Aplikacije"</string>
diff --git a/res/values-sq-rAL/strings.xml b/res/values-sq-rAL/strings.xml
index 65aabf6..2b535a3 100644
--- a/res/values-sq-rAL/strings.xml
+++ b/res/values-sq-rAL/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Kërko për aplikacione"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Po ngarkon aplikacionet..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Nuk u gjet asnjë aplikacion që përputhet me \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Shko te <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Nuk ka më hapësirë në këtë ekran bazë."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Nuk ka më hapësirë në tabakanë \"Të preferuarat\""</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Aplikacionet"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 0f93bf3..f21aae0 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Претражите апликације"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Апликације се учитавају..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Није пронађена ниједна апликација за „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Иди на апликацију <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Нема више простора на овом почетном екрану."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Нема више простора на траци Омиљено"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Апликације"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 93fce80..385d7a6 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Sök efter appar"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Läser in appar …"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Det gick inte att hitta några appar som matchar <xliff:g id="QUERY">%1$s</xliff:g>"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Gå till <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Det finns inte plats för mer på den här startskärmen."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Favoritfältet är fullt"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Appar"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 297b731..b86fb73 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Tafuta Programu"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Inapakia Programu..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Haikupata programu zinazolingana na \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Nenda kwenye <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Hakuna nafasi katika skrini hii ya Mwanzo."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Hakuna nafasi zaidi katika treya ya Vipendeleo"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Programu"</string>
diff --git a/res/values-sw600dp/config.xml b/res/values-sw600dp/config.xml
index a7345a7..eb9af97 100644
--- a/res/values-sw600dp/config.xml
+++ b/res/values-sw600dp/config.xml
@@ -1,7 +1,4 @@
 <resources>
     <bool name="is_tablet">true</bool>
     <bool name="allow_rotation">true</bool>
-
-<!-- DragController -->
-    <integer name="config_flingToDeleteMinVelocity">-1000</integer>
 </resources>
diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml
index 2651fbb..c1ac9aa 100644
--- a/res/values-sw600dp/dimens.xml
+++ b/res/values-sw600dp/dimens.xml
@@ -28,4 +28,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-ta-rIN/strings.xml b/res/values-ta-rIN/strings.xml
index 1f2a6f9..9dac6d0 100644
--- a/res/values-ta-rIN/strings.xml
+++ b/res/values-ta-rIN/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"பயன்பாடுகளில் தேடுக"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"பயன்பாடுகளை ஏற்றுகிறது..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" உடன் பொருந்தும் பயன்பாடுகள் இல்லை"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g>க்குச் செல்லவும்"</string>
     <string name="out_of_space" msgid="4691004494942118364">"முகப்புத் திரையில் இடமில்லை."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"பிடித்தவை ட்ரேயில் இடமில்லை"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"பயன்பாடுகள்"</string>
diff --git a/res/values-te-rIN/strings.xml b/res/values-te-rIN/strings.xml
index bd5fd70..32cb292 100644
--- a/res/values-te-rIN/strings.xml
+++ b/res/values-te-rIN/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"అనువర్తనాలను శోధించండి"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"అనువర్తనాలను లోడ్ చేస్తోంది…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\"కి సరిపోలే అనువర్తనాలేవీ కనుగొనబడలేదు"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g>కి వెళ్లు"</string>
     <string name="out_of_space" msgid="4691004494942118364">"ఈ హోమ్ స్క్రీన్‌లో ఖాళీ లేదు."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"ఇష్టమైనవి ట్రేలో ఖాళీ లేదు"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"అనువర్తనాలు"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 85919af..53ae589 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ค้นหาแอป"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"กำลังโหลดแอป…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"ไม่พบแอปที่ตรงกับ \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"ไปที่ <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"ไม่มีที่ว่างในหน้าจอหลักนี้"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"ไม่มีพื้นที่เหลือในถาดรายการโปรด"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"แอป"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 69a5bb7..43afeab 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Mga App sa Paghahanap"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Nilo-load ang Mga App…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Walang nakitang Mga App na tumutugma sa \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Pumunta sa <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Wala nang lugar sa Home screen na ito."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Wala nang lugar sa tray ng Mga Paborito"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index d933c87..064d049 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Uygulamalarda Ara"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Uygulamalar Yükleniyor…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" ile eşleşen uygulama bulunamadı"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> uygulamasına git"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Bu Ana ekranda yer kalmadı."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Favoriler tepsisinde başka yer kalmadı"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Uygulamalar"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index b06177e..ff9364e 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Пошук додатків"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Завантаження додатків…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Немає додатків для запиту \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Перейти в додаток <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"На цьому головному екрані більше немає місця."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"В області \"Вибране\" немає місця"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Додатки"</string>
diff --git a/res/values-ur-rPK/strings.xml b/res/values-ur-rPK/strings.xml
index ba189c8..f3d6311 100644
--- a/res/values-ur-rPK/strings.xml
+++ b/res/values-ur-rPK/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ایپس تلاش کریں"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"ایپس لوڈ ہو رہی ہیں…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" سے مماثل کوئی ایپس نہیں ملیں"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> پر جائیں"</string>
     <string name="out_of_space" msgid="4691004494942118364">"اس ہوم اسکرین پر مزید کوئی گنجائش نہیں ہے۔"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"پسندیدہ ٹرے میں مزید کوئی گنجائش نہیں ہے"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"ایپس"</string>
diff --git a/res/values-uz-rUZ/strings.xml b/res/values-uz-rUZ/strings.xml
index c3bffeb..24e4c4d 100644
--- a/res/values-uz-rUZ/strings.xml
+++ b/res/values-uz-rUZ/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Ilovalarni qidirish"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Ilovalar yuklanmoqda…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"“<xliff:g id="QUERY">%1$s</xliff:g>” bilan mos hech qanday ilova topilmadi"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"O‘tish: <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Uy ekranida bitta ham xona yo‘q."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Ajratilganlarda birorta ham xona yo‘q"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Ilovalar"</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index f57f491..6eff507 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Tìm kiếm ứng dụng"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Đang tải ứng dụng..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Không tìm thấy ứng dụng nào phù hợp với \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Chuyển tới <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Không còn chỗ trên Màn hình chính này."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Không còn chỗ trong khay Mục yêu thích"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Ứng dụng"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 82274ba..77f07c9 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"搜索应用"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"正在加载应用…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"未找到与“<xliff:g id="QUERY">%1$s</xliff:g>”相符的应用"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"转到 <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"此主屏幕上已没有空间。"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"收藏栏已满"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"应用"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index f7d240e..62032ec 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"搜尋應用程式"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"正在載入應用程式…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"無法找到與「<xliff:g id="QUERY">%1$s</xliff:g>」相符的應用程式"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"前往 <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"主畫面已無空間。"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"我的收藏寄存區沒有足夠空間"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"應用程式"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 39a0ec7..0b3f4d6 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"搜尋應用程式"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"正在載入應用程式…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"找不到符合「<xliff:g id="QUERY">%1$s</xliff:g>」的應用程式"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"前往 <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"這個主螢幕已無空間。"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"「我的最愛」匣已無可用空間"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"應用程式"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 83ba089..bb49f1f 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Sesha Izinhlelo Zokusebenza"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Ilayisha izinhlelo zokusebenza..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Azikho izinhlelo zokusebenza ezitholakele ezifana ne-\"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Hamba ku-<xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Asisekho isikhala kulesi sikrini Sasekhaya."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Asisekho isikhala kwitreyi lezintandokazi"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Izinhlelo zokusebenza"</string>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 51e4d40..6fe2ae1 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -39,11 +39,12 @@
     <color name="outline_color">#FFFFFFFF</color>
 
     <!-- Containers -->
-    <color name="container_fastscroll_thumb_inactive_color">#42000000</color>
+    <color name="container_fastscroll_thumb_inactive_color">#009688</color>
     <color name="container_fastscroll_thumb_active_color">#009688</color>
 
     <!-- All Apps -->
     <color name="all_apps_grid_section_text_color">#009688</color>
+    <color name="all_apps_search_market_button_focused_bg_color">#DDDDDD</color>
 
     <!-- Widgets view -->
     <color name="widgets_view_fastscroll_thumb_inactive_color">#42FFFFFF</color>
diff --git a/res/values/config.xml b/res/values/config.xml
index 73de794..6a67840 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -2,8 +2,6 @@
 <!-- Dynamic Grid -->
     <!-- Out of 100, the percent of space the overview bar should try and take vertically. -->
     <integer name="config_dynamic_grid_overview_icon_zone_percentage">20</integer>
-    <!-- Out of 100, the percent to shrink the workspace during overview mode. -->
-    <integer name="config_dynamic_grid_overview_scale_percentage">80</integer>
 
 <!-- Miscellaneous -->
     <bool name="config_largeHeap">false</bool>
@@ -19,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 -->
@@ -30,6 +27,8 @@
 
     <!-- Out of 100, the percent to shrink the workspace during spring loaded mode. -->
     <integer name="config_workspaceSpringLoadShrinkPercentage">80</integer>
+    <!-- Out of 100, the percent to shrink the workspace during overview mode. -->
+    <integer name="config_workspaceOverviewShrinkPercentage">70</integer>
 
     <!-- Fade/zoom in/out duration & scale in a Launcher overlay transition.
          Note: This should be less than the config_overlayTransitionTime as they happen together. -->
@@ -74,10 +73,6 @@
          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>
-
 <!-- 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 589d696..5430872 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -55,9 +55,9 @@
     <!-- Notes: container_bounds_inset - quantum_panel_outer_padding -->
     <dimen name="container_bounds_minus_quantum_panel_padding_inset">4dp</dimen>
 
-    <dimen name="container_fastscroll_thumb_min_width">4dp</dimen>
-    <dimen name="container_fastscroll_thumb_max_width">8dp</dimen>
-    <dimen name="container_fastscroll_thumb_height">64dp</dimen>
+    <dimen name="container_fastscroll_thumb_min_width">5dp</dimen>
+    <dimen name="container_fastscroll_thumb_max_width">9dp</dimen>
+    <dimen name="container_fastscroll_thumb_height">72dp</dimen>
     <dimen name="container_fastscroll_thumb_touch_inset">-24dp</dimen>
     <dimen name="container_fastscroll_popup_size">72dp</dimen>
     <dimen name="container_fastscroll_popup_text_size">48dp</dimen>
@@ -85,6 +85,7 @@
     <dimen name="widget_section_icon_size">40dp</dimen>
     <dimen name="widget_section_vertical_padding">8dp</dimen>
     <dimen name="widget_section_horizontal_padding">16dp</dimen>
+    <dimen name="widget_section_indent">0dp</dimen>
 
     <dimen name="widget_row_padding">8dp</dimen>
     <dimen name="widget_row_divider">2dp</dimen>
@@ -111,6 +112,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>
 
@@ -129,4 +132,8 @@
     <dimen name="blur_size_click_shadow">4dp</dimen>
     <dimen name="click_shadow_high_shift">2dp</dimen>
 
+<!-- Pending widget -->
+    <dimen name="pending_widget_min_padding">8dp</dimen>
+    <dimen name="pending_widget_elevation">2dp</dimen>
+
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 88f149b..fefadef 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -24,10 +24,10 @@
     <!-- URI used to import old favorites. [DO NOT TRANSLATE] -->
     <string name="old_launcher_provider_uri" translatable="false">content://com.android.launcher2.settings/favorites?notify=true</string>
 
-    <!-- Permission to receive the com.android.launcher3.action.LAUNCH intent -->
+    <!-- Permission to receive the com.android.launcher3.action.LAUNCH intent. [DO NOT TRANSLATE] -->
     <string name="receive_launch_broadcasts_permission" translatable="false">com.android.launcher3.permission.RECEIVE_LAUNCH_BROADCASTS</string>
 
-    <!-- Permission to receive the com.android.launcher3.action.FIRST_LOAD_COMPLETE intent -->
+    <!-- Permission to receive the com.android.launcher3.action.FIRST_LOAD_COMPLETE intent. [DO NOT TRANSLATE] -->
     <string name="receive_first_load_broadcast_permission" translatable="false">com.android.launcher3.permission.RECEIVE_FIRST_LOAD_BROADCAST</string>
 
     <!-- Application name -->
@@ -61,6 +61,9 @@
     <string name="all_apps_loading_message">Loading Apps&#8230;</string>
     <!-- No-search-results text. [CHAR_LIMIT=50] -->
     <string name="all_apps_no_search_results">No Apps found matching \"<xliff:g id="query" example="Android">%1$s</xliff:g>\"</string>
+    <!-- Search market text.  This is a format string where the first argument is the name of the activity
+         handling the search.  The format string does not need to handle both of these arguments. [CHAR_LIMIT=50] -->
+    <string name="all_apps_search_market_message">Go to <xliff:g id="query" example="Play Store">%1$s</xliff:g></string>
 
     <!-- Drag and drop -->
     <skip />
diff --git a/res/xml/default_workspace_4x4.xml b/res/xml/default_workspace_4x4.xml
index 9bec86a..060a1f8 100644
--- a/res/xml/default_workspace_4x4.xml
+++ b/res/xml/default_workspace_4x4.xml
@@ -15,102 +15,33 @@
 -->
 
 <favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
-    <!-- Far-left screen [0] -->
 
-    <!-- Left screen [1] -->
-    <appwidget
-        launcher:packageName="com.android.settings"
-        launcher:className="com.android.settings.widget.SettingsAppWidgetProvider"
-        launcher:screen="1"
-        launcher:x="0"
-        launcher:y="3"
-        launcher:spanX="4"
-        launcher:spanY="1" />
+    <!-- Hotseat -->
+    <include launcher:workspace="@xml/dw_phone_hotseat" />
 
-    <!-- Middle screen [2] -->
-    <appwidget
-        launcher:packageName="com.android.deskclock"
-        launcher:className="com.android.alarmclock.AnalogAppWidgetProvider"
-        launcher:screen="2"
-        launcher:x="1"
-        launcher:y="0"
-        launcher:spanX="2"
-        launcher:spanY="2" />
-    <favorite
-        launcher:packageName="com.android.camera"
-        launcher:className="com.android.camera.Camera"
-        launcher:screen="2"
-        launcher:x="0"
-        launcher:y="3" />
-
-    <!-- Right screen [3] -->
-    <favorite
-        launcher:packageName="com.android.gallery3d"
-        launcher:className="com.android.gallery3d.app.Gallery"
-        launcher:screen="3"
-        launcher:x="1"
-        launcher:y="3" />
-    <favorite
-        launcher:packageName="com.android.settings"
-        launcher:className="com.android.settings.Settings"
-        launcher:screen="3"
-        launcher:x="2"
-        launcher:y="3" />
-
-    <!-- Far-right screen [4] -->
-
-    <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
-    <!-- Dialer, Contacts, [All Apps], Messaging, Browser -->
+    <!-- Bottom row -->
     <resolve
-        launcher:container="-101"
         launcher:screen="0"
         launcher:x="0"
-        launcher:y="0" >
-        <favorite launcher:uri="#Intent;action=android.intent.action.DIAL;end" />
-        <favorite launcher:uri="tel:123" />
-        <favorite launcher:uri="#Intent;action=android.intent.action.CALL_BUTTON;end" />
-
-        <favorite
-            launcher:packageName="com.android.dialer"
-            launcher:className="com.android.dialer.DialtactsActivity" />
+        launcher:y="3" >
+        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_EMAIL;end" />
+        <favorite launcher:uri="mailto:" />
     </resolve>
 
-    <favorite
-        launcher:packageName="com.android.contacts"
-        launcher:className="com.android.contacts.activities.PeopleActivity"
-        launcher:container="-101"
-        launcher:screen="1"
+    <resolve
+        launcher:screen="0"
         launcher:x="1"
-        launcher:y="0" />
-
-    <resolve
-        launcher:container="-101"
-        launcher:screen="3"
-        launcher:x="3"
-        launcher:y="0" >
-        <favorite
-            launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MESSAGING;end" />
-        <favorite launcher:uri="sms:" />
-        <favorite launcher:uri="smsto:" />
-        <favorite launcher:uri="mms:" />
-        <favorite launcher:uri="mmsto:" />
-
-        <favorite
-            launcher:packageName="com.android.mms"
-            launcher:className="com.android.mms.ui.ConversationList" />
+        launcher:y="3" >
+        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_GALLERY;end" />
+        <favorite launcher:uri="#Intent;type=images/*;end" />
     </resolve>
-    <resolve
-        launcher:container="-101"
-        launcher:screen="4"
-        launcher:x="4"
-        launcher:y="0" >
-        <favorite
-            launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_BROWSER;end" />
-        <favorite launcher:uri="http://www.example.com/" />
 
-        <favorite
-            launcher:packageName="com.android.browser"
-            launcher:className="com.android.browser.BrowserActivity" />
+    <resolve
+        launcher:screen="0"
+        launcher:x="3"
+        launcher:y="3" >
+        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MARKET;end" />
+        <favorite launcher:uri="market://details?id=com.android.launcher" />
     </resolve>
 
 </favorites>
diff --git a/res/xml/default_workspace_5x5.xml b/res/xml/default_workspace_5x5.xml
index 9bec86a..3226617 100644
--- a/res/xml/default_workspace_5x5.xml
+++ b/res/xml/default_workspace_5x5.xml
@@ -15,102 +15,34 @@
 -->
 
 <favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
-    <!-- Far-left screen [0] -->
 
-    <!-- Left screen [1] -->
-    <appwidget
-        launcher:packageName="com.android.settings"
-        launcher:className="com.android.settings.widget.SettingsAppWidgetProvider"
-        launcher:screen="1"
-        launcher:x="0"
-        launcher:y="3"
-        launcher:spanX="4"
-        launcher:spanY="1" />
+    <!-- Hotseat -->
+    <include launcher:workspace="@xml/dw_phone_hotseat" />
 
-    <!-- Middle screen [2] -->
-    <appwidget
-        launcher:packageName="com.android.deskclock"
-        launcher:className="com.android.alarmclock.AnalogAppWidgetProvider"
-        launcher:screen="2"
-        launcher:x="1"
-        launcher:y="0"
-        launcher:spanX="2"
-        launcher:spanY="2" />
-    <favorite
-        launcher:packageName="com.android.camera"
-        launcher:className="com.android.camera.Camera"
-        launcher:screen="2"
-        launcher:x="0"
-        launcher:y="3" />
-
-    <!-- Right screen [3] -->
-    <favorite
-        launcher:packageName="com.android.gallery3d"
-        launcher:className="com.android.gallery3d.app.Gallery"
-        launcher:screen="3"
-        launcher:x="1"
-        launcher:y="3" />
-    <favorite
-        launcher:packageName="com.android.settings"
-        launcher:className="com.android.settings.Settings"
-        launcher:screen="3"
-        launcher:x="2"
-        launcher:y="3" />
-
-    <!-- Far-right screen [4] -->
-
-    <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
-    <!-- Dialer, Contacts, [All Apps], Messaging, Browser -->
+    <!-- Bottom row -->
     <resolve
-        launcher:container="-101"
         launcher:screen="0"
         launcher:x="0"
-        launcher:y="0" >
-        <favorite launcher:uri="#Intent;action=android.intent.action.DIAL;end" />
-        <favorite launcher:uri="tel:123" />
-        <favorite launcher:uri="#Intent;action=android.intent.action.CALL_BUTTON;end" />
+        launcher:y="4" >
+	    <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_EMAIL;end" />
+	    <favorite launcher:uri="mailto:" />
 
-        <favorite
-            launcher:packageName="com.android.dialer"
-            launcher:className="com.android.dialer.DialtactsActivity" />
     </resolve>
 
-    <favorite
-        launcher:packageName="com.android.contacts"
-        launcher:className="com.android.contacts.activities.PeopleActivity"
-        launcher:container="-101"
-        launcher:screen="1"
+    <resolve
+        launcher:screen="0"
         launcher:x="1"
-        launcher:y="0" />
+        launcher:y="4" >
+	    <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_GALLERY;end" />
+	    <favorite launcher:uri="#Intent;type=images/*;end" />
 
-    <resolve
-        launcher:container="-101"
-        launcher:screen="3"
-        launcher:x="3"
-        launcher:y="0" >
-        <favorite
-            launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MESSAGING;end" />
-        <favorite launcher:uri="sms:" />
-        <favorite launcher:uri="smsto:" />
-        <favorite launcher:uri="mms:" />
-        <favorite launcher:uri="mmsto:" />
-
-        <favorite
-            launcher:packageName="com.android.mms"
-            launcher:className="com.android.mms.ui.ConversationList" />
     </resolve>
+
     <resolve
-        launcher:container="-101"
-        launcher:screen="4"
+        launcher:screen="0"
         launcher:x="4"
-        launcher:y="0" >
-        <favorite
-            launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_BROWSER;end" />
-        <favorite launcher:uri="http://www.example.com/" />
-
-        <favorite
-            launcher:packageName="com.android.browser"
-            launcher:className="com.android.browser.BrowserActivity" />
+        launcher:y="4" >
+        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MARKET;end" />
+        <favorite launcher:uri="market://details?id=com.android.launcher" />
     </resolve>
-
 </favorites>
diff --git a/res/xml/default_workspace_5x6.xml b/res/xml/default_workspace_5x6.xml
index d42a93a..bc236fb 100644
--- a/res/xml/default_workspace_5x6.xml
+++ b/res/xml/default_workspace_5x6.xml
@@ -15,101 +15,23 @@
 -->
 
 <favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
-    <!-- Far-left screen [0] -->
 
-    <!-- Left screen [1] -->
-    <appwidget
-        launcher:packageName="com.android.settings"
-        launcher:className="com.android.settings.widget.SettingsAppWidgetProvider"
-        launcher:screen="1"
+    <!-- Hotseat -->
+    <include launcher:workspace="@xml/dw_tablet_hotseat" />
+
+    <!-- Bottom row -->
+    <favorite
+        launcher:screen="0"
         launcher:x="0"
-        launcher:y="3"
-        launcher:spanX="4"
-        launcher:spanY="1" />
-
-    <!-- Middle screen [2] -->
-    <appwidget
-        launcher:packageName="com.android.deskclock"
-        launcher:className="com.android.alarmclock.AnalogAppWidgetProvider"
-        launcher:screen="2"
-        launcher:x="1"
-        launcher:y="0"
-        launcher:spanX="2"
-        launcher:spanY="2" />
-    <favorite
-        launcher:packageName="com.android.camera"
-        launcher:className="com.android.camera.Camera"
-        launcher:screen="2"
-        launcher:x="0"
-        launcher:y="3" />
-
-    <!-- Right screen [3] -->
-    <favorite
-        launcher:packageName="com.android.gallery3d"
-        launcher:className="com.android.gallery3d.app.Gallery"
-        launcher:screen="3"
-        launcher:x="1"
-        launcher:y="3" />
-    <favorite
-        launcher:packageName="com.android.settings"
-        launcher:className="com.android.settings.Settings"
-        launcher:screen="3"
-        launcher:x="2"
-        launcher:y="3" />
-
-    <!-- Far-right screen [4] -->
-
-    <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
-    <!-- Dialer, Contacts, [All Apps], Messaging, Browser -->
-    <resolve
-        launcher:container="-101"
-        launcher:screen="1"
-        launcher:x="1"
-        launcher:y="0" >
-        <favorite launcher:uri="#Intent;action=android.intent.action.DIAL;end" />
-        <favorite launcher:uri="tel:123" />
-        <favorite launcher:uri="#Intent;action=android.intent.action.CALL_BUTTON;end" />
-
-        <favorite
-            launcher:packageName="com.android.dialer"
-            launcher:className="com.android.dialer.DialtactsActivity" />
-    </resolve>
-
-    <favorite
-        launcher:packageName="com.android.contacts"
-        launcher:className="com.android.contacts.activities.PeopleActivity"
-        launcher:container="-101"
-        launcher:screen="2"
-        launcher:x="2"
-        launcher:y="0" />
+        launcher:y="4"
+        launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_CONTACTS;end" />
 
     <resolve
-        launcher:container="-101"
-        launcher:screen="4"
-        launcher:x="4"
-        launcher:y="0" >
-        <favorite
-            launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MESSAGING;end" />
-        <favorite launcher:uri="sms:" />
-        <favorite launcher:uri="smsto:" />
-        <favorite launcher:uri="mms:" />
-        <favorite launcher:uri="mmsto:" />
-
-        <favorite
-            launcher:packageName="com.android.mms"
-            launcher:className="com.android.mms.ui.ConversationList" />
-    </resolve>
-    <resolve
-        launcher:container="-101"
-        launcher:screen="5"
+        launcher:screen="0"
         launcher:x="5"
-        launcher:y="0" >
-        <favorite
-            launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_BROWSER;end" />
-        <favorite launcher:uri="http://www.example.com/" />
-
-        <favorite
-            launcher:packageName="com.android.browser"
-            launcher:className="com.android.browser.BrowserActivity" />
+        launcher:y="4" >
+        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MARKET;end" />
+        <favorite launcher:uri="market://details?id=com.android.launcher" />
     </resolve>
+
 </favorites>
diff --git a/res/xml/dw_phone_hotseat.xml b/res/xml/dw_phone_hotseat.xml
new file mode 100644
index 0000000..b58994d
--- /dev/null
+++ b/res/xml/dw_phone_hotseat.xml
@@ -0,0 +1,63 @@
+<?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.
+-->
+
+<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
+    <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
+    <!-- Dialer, Messaging, [All Apps], Browser, Camera -->
+    <resolve
+        launcher:container="-101"
+        launcher:screen="0"
+        launcher:x="0"
+        launcher:y="0" >
+        <favorite launcher:uri="#Intent;action=android.intent.action.DIAL;end" />
+        <favorite launcher:uri="tel:123" />
+        <favorite launcher:uri="#Intent;action=android.intent.action.CALL_BUTTON;end" />
+    </resolve>
+
+    <resolve
+        launcher:container="-101"
+        launcher:screen="1"
+        launcher:x="1"
+        launcher:y="0" >
+        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MESSAGING;end" />
+        <favorite launcher:uri="sms:" />
+        <favorite launcher:uri="smsto:" />
+        <favorite launcher:uri="mms:" />
+        <favorite launcher:uri="mmsto:" />
+    </resolve>
+
+    <!-- All Apps -->
+
+    <resolve
+        launcher:container="-101"
+        launcher:screen="3"
+        launcher:x="3"
+        launcher:y="0" >
+        <favorite
+            launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_BROWSER;end" />
+        <favorite launcher:uri="http://www.example.com/" />
+    </resolve>
+
+    <resolve
+        launcher:container="-101"
+        launcher:screen="4"
+        launcher:x="4"
+        launcher:y="0" >
+        <favorite launcher:uri="#Intent;action=android.media.action.STILL_IMAGE_CAMERA;end" />
+        <favorite launcher:uri="#Intent;action=android.intent.action.CAMERA_BUTTON;end" />
+    </resolve>
+
+</favorites>
diff --git a/res/xml/dw_tablet_hotseat.xml b/res/xml/dw_tablet_hotseat.xml
new file mode 100644
index 0000000..671ccba
--- /dev/null
+++ b/res/xml/dw_tablet_hotseat.xml
@@ -0,0 +1,78 @@
+<?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.
+-->
+
+<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
+    <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
+    <!-- Messaging, Email, Browser, [All Apps], Music, Gallery, Camera -->
+    <resolve
+        launcher:container="-101"
+        launcher:screen="0"
+        launcher:x="0"
+        launcher:y="0" >
+        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MESSAGING;end" />
+        <favorite launcher:uri="sms:" />
+        <favorite launcher:uri="smsto:" />
+        <favorite launcher:uri="mms:" />
+        <favorite launcher:uri="mmsto:" />
+    </resolve>
+
+    <resolve
+        launcher:container="-101"
+        launcher:screen="1"
+        launcher:x="1"
+        launcher:y="0" >
+        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_EMAIL;end" />
+        <favorite launcher:uri="mailto:" />
+    </resolve>
+
+    <resolve
+        launcher:container="-101"
+        launcher:screen="2"
+        launcher:x="2"
+        launcher:y="0" >
+        <favorite
+            launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_BROWSER;end" />
+        <favorite launcher:uri="http://www.example.com/" />
+    </resolve>
+
+    <!-- All Apps -->
+
+    <favorite
+        launcher:container="-101"
+        launcher:screen="4"
+        launcher:x="4"
+        launcher:y="0"
+        launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MUSIC;end" />
+
+    <resolve
+        launcher:container="-101"
+        launcher:screen="5"
+        launcher:x="5"
+        launcher:y="0" >
+        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_GALLERY;end" />
+        <favorite launcher:uri="#Intent;type=images/*;end" />
+    </resolve>
+
+    <resolve
+        launcher:container="-101"
+        launcher:screen="6"
+        launcher:x="6"
+        launcher:y="0" >
+        <favorite launcher:uri="#Intent;action=android.media.action.STILL_IMAGE_CAMERA;end" />
+        <favorite launcher:uri="#Intent;action=android.intent.action.CAMERA_BUTTON;end" />
+    </resolve>
+
+</favorites>
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/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index ea7c221..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;
@@ -75,8 +81,8 @@
         mResizeMode = info.resizeMode;
         mDragLayer = dragLayer;
 
-        mMinHSpan = info.getMinSpanX(mLauncher);
-        mMinVSpan = info.getMinSpanY(mLauncher);
+        mMinHSpan = info.minSpanX;
+        mMinVSpan = info.minSpanY;
 
         setBackgroundResource(R.drawable.widget_resize_shadow);
         setForeground(getResources().getDrawable(R.drawable.widget_resize_frame));
@@ -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 5e7a012..10fdd87 100644
--- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
+++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
@@ -48,7 +48,8 @@
             final AppWidgetProviderInfo provider = widgets.getAppWidgetInfo(newWidgetIds[i]);
             final int state;
             if (LauncherModel.isValidProvider(provider)) {
-                state = LauncherAppWidgetInfo.RESTORE_COMPLETED;
+                // This will ensure that we show 'Click to setup' UI if required.
+                state = LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
             } else {
                 state = LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
             }
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index 99a98dd..440e4e7 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -355,7 +355,7 @@
                     return addShortcut(info.loadLabel(mPackageManager).toString(),
                             intent, Favorites.ITEM_TYPE_APPLICATION);
                 } catch (PackageManager.NameNotFoundException e) {
-                    if (LOGD) Log.w(TAG, "Unable to add favorite: " + packageName + "/" + className, e);
+                    Log.e(TAG, "Unable to add favorite: " + packageName + "/" + className, e);
                 }
                 return -1;
             } else {
@@ -367,7 +367,7 @@
          * Helper method to allow extending the parser capabilities
          */
         protected long invalidPackageOrClass(XmlResourceParser parser) {
-            if (LOGD) Log.d(TAG, "Skipping invalid <favorite> with no component");
+            Log.w(TAG, "Skipping invalid <favorite> with no component");
             return -1;
         }
     }
diff --git a/src/com/android/launcher3/BaseContainerView.java b/src/com/android/launcher3/BaseContainerView.java
index c8de9df..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.
  */
@@ -34,10 +36,12 @@
     // The bounds of the search bar.  Only the left, top, right are used to inset the
     // search bar and the height is determined by the measurement of the layout
     private Rect mFixedSearchBarBounds = new Rect();
-    // The bounds of the container
+    // The computed bounds of the search bar
+    private Rect mSearchBarBounds = new Rect();
+    // The computed bounds of the container
     protected Rect mContentBounds = new Rect();
-    // The padding to apply to the container to achieve the bounds
-    protected Rect mContentPadding = new Rect();
+    // The computed padding to apply to the container to achieve the container bounds
+    private Rect mContentPadding = new Rect();
     // The inset to apply to the edges and between the search bar and the container
     private int mContainerBoundsInset;
     private boolean mHasSearchBar;
@@ -69,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);
         }
 
@@ -90,7 +94,7 @@
      */
     protected void updateBackgroundAndPaddings() {
         Rect padding;
-        Rect searchBarBounds = new Rect(mFixedSearchBarBounds);
+        Rect searchBarBounds = new Rect();
         if (!isValidSearchBarBounds(mFixedSearchBarBounds)) {
             // Use the default bounds
             padding = new Rect(mInsets.left + mContainerBoundsInset,
@@ -110,14 +114,20 @@
                     (mHasSearchBar ? 0 : (mInsets.top + mContainerBoundsInset)),
                     getMeasuredWidth() - mFixedSearchBarBounds.right,
                     mInsets.bottom + mContainerBoundsInset);
+
+            // Use the search bounds
+            searchBarBounds.set(mFixedSearchBarBounds);
         }
-        if (!padding.equals(mContentPadding) || !searchBarBounds.equals(mFixedSearchBarBounds)) {
+
+        // If either the computed container padding has changed, or the computed search bar bounds
+        // has changed, then notify the container
+        if (!padding.equals(mContentPadding) || !searchBarBounds.equals(mSearchBarBounds)) {
             mContentPadding.set(padding);
             mContentBounds.set(padding.left, padding.top,
                     getMeasuredWidth() - padding.right,
                     getMeasuredHeight() - padding.bottom);
-            mFixedSearchBarBounds.set(searchBarBounds);
-            onUpdateBackgroundAndPaddings(mFixedSearchBarBounds, padding);
+            mSearchBarBounds.set(searchBarBounds);
+            onUpdateBackgroundAndPaddings(mSearchBarBounds, padding);
         }
     }
 
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 0fae427..f0d8b3b 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -92,9 +92,15 @@
             // TODO(winsonc): If we want to animate the section heads while scrolling, we can
             //                initiate that here if the recycler view scroll state is not
             //                RecyclerView.SCROLL_STATE_IDLE.
+
+            onUpdateScrollbar(dy);
         }
     }
 
+    public void reset() {
+        mScrollbar.reattachThumbToScroll();
+    }
+
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
@@ -143,7 +149,7 @@
                 mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
                 break;
         }
-        return mScrollbar.isDragging();
+        return mScrollbar.isDraggingThumb();
     }
 
     public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
@@ -185,12 +191,10 @@
      *   AvailableScrollHeight = Total height of the all items - last page height
      *
      * This assumes that all rows are the same height.
-     *
-     * @param yOffset the offset from the top of the recycler view to start tracking.
      */
-    protected int getAvailableScrollHeight(int rowCount, int rowHeight, int yOffset) {
+    protected int getAvailableScrollHeight(int rowCount, int rowHeight) {
         int visibleHeight = getHeight() - mBackgroundPadding.top - mBackgroundPadding.bottom;
-        int scrollHeight = getPaddingTop() + yOffset + rowCount * rowHeight + getPaddingBottom();
+        int scrollHeight = getPaddingTop() + rowCount * rowHeight + getPaddingBottom();
         int availableScrollHeight = scrollHeight - visibleHeight;
         return availableScrollHeight;
     }
@@ -222,7 +226,7 @@
     @Override
     protected void dispatchDraw(Canvas canvas) {
         super.dispatchDraw(canvas);
-        onUpdateScrollbar();
+        onUpdateScrollbar(0);
         mScrollbar.draw(canvas);
     }
 
@@ -234,24 +238,21 @@
      * @param scrollPosState the current scroll position
      * @param rowCount the number of rows, used to calculate the total scroll height (assumes that
      *                 all rows are the same height)
-     * @param yOffset the offset to start tracking in the recycler view (only used for all apps)
      */
     protected void synchronizeScrollBarThumbOffsetToViewScroll(ScrollPositionState scrollPosState,
-            int rowCount, int yOffset) {
-        int availableScrollHeight = getAvailableScrollHeight(rowCount, scrollPosState.rowHeight,
-                yOffset);
-        int availableScrollBarHeight = getAvailableScrollBarHeight();
-
+            int rowCount) {
         // Only show the scrollbar if there is height to be scrolled
+        int availableScrollBarHeight = getAvailableScrollBarHeight();
+        int availableScrollHeight = getAvailableScrollHeight(rowCount, scrollPosState.rowHeight);
         if (availableScrollHeight <= 0) {
-            mScrollbar.setScrollbarThumbOffset(-1, -1);
+            mScrollbar.setThumbOffset(-1, -1);
             return;
         }
 
         // Calculate the current scroll position, the scrollY of the recycler view accounts for the
         // view padding, while the scrollBarY is drawn right up to the background padding (ignoring
         // padding)
-        int scrollY = getPaddingTop() + yOffset +
+        int scrollY = getPaddingTop() +
                 (scrollPosState.rowIndex * scrollPosState.rowHeight) - scrollPosState.rowTopOffset;
         int scrollBarY = mBackgroundPadding.top +
                 (int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
@@ -261,9 +262,9 @@
         if (Utilities.isRtl(getResources())) {
             scrollBarX = mBackgroundPadding.left;
         } else {
-            scrollBarX = getWidth() - mBackgroundPadding.right - mScrollbar.getWidth();
+            scrollBarX = getWidth() - mBackgroundPadding.right - mScrollbar.getThumbWidth();
         }
-        mScrollbar.setScrollbarThumbOffset(scrollBarX, scrollBarY);
+        mScrollbar.setThumbOffset(scrollBarX, scrollBarY);
     }
 
     /**
@@ -276,10 +277,15 @@
      * Updates the bounds for the scrollbar.
      * <p>Override in each subclass of this base class.
      */
-    public abstract void onUpdateScrollbar();
+    public abstract void onUpdateScrollbar(int dy);
 
     /**
      * <p>Override in each subclass of this base class.
      */
     public void onFastScrollCompleted() {}
+
+    /**
+     * Returns information about the item that the recycler view is currently scrolled to.
+     */
+    protected abstract void getCurScrollState(ScrollPositionState stateOut);
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
index 2c4184d..f76aed7 100644
--- a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
+++ b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
@@ -23,6 +23,7 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
+import android.graphics.Path;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.view.MotionEvent;
@@ -51,14 +52,20 @@
     private int mThumbActiveColor;
     @Thunk Point mThumbOffset = new Point(-1, -1);
     @Thunk Paint mThumbPaint;
-    private Paint mTrackPaint;
     private int mThumbMinWidth;
     private int mThumbMaxWidth;
     @Thunk int mThumbWidth;
     @Thunk int mThumbHeight;
+    private int mThumbCurvature;
+    private Path mThumbPath = new Path();
+    private Paint mTrackPaint;
+    private int mTrackWidth;
+    private float mLastTouchY;
     // The inset is the buffer around which a point will still register as a click on the scrollbar
     private int mTouchInset;
     private boolean mIsDragging;
+    private boolean mIsThumbDetached;
+    private boolean mCanThumbDetach;
 
     // This is the offset from the top of the scrollbar when the user first starts touching.  To
     // prevent jumping, this offset is applied as the user scrolls.
@@ -72,51 +79,74 @@
         mPopup = new BaseRecyclerViewFastScrollPopup(rv, res);
         mTrackPaint = new Paint();
         mTrackPaint.setColor(rv.getFastScrollerTrackColor(Color.BLACK));
-        mTrackPaint.setAlpha(0);
+        mTrackPaint.setAlpha(MAX_TRACK_ALPHA);
         mThumbInactiveColor = rv.getFastScrollerThumbInactiveColor(
                 res.getColor(R.color.container_fastscroll_thumb_inactive_color));
         mThumbActiveColor = res.getColor(R.color.container_fastscroll_thumb_active_color);
         mThumbPaint = new Paint();
+        mThumbPaint.setAntiAlias(true);
         mThumbPaint.setColor(mThumbInactiveColor);
+        mThumbPaint.setStyle(Paint.Style.FILL);
         mThumbWidth = mThumbMinWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_min_width);
         mThumbMaxWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_max_width);
         mThumbHeight = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_height);
+        mThumbCurvature = mThumbMaxWidth - mThumbMinWidth;
         mTouchInset = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_touch_inset);
     }
 
-    public void setScrollbarThumbOffset(int x, int y) {
+    public void setDetachThumbOnFastScroll() {
+        mCanThumbDetach = true;
+    }
+
+    public void reattachThumbToScroll() {
+        mIsThumbDetached = false;
+    }
+
+    public void setThumbOffset(int x, int y) {
         if (mThumbOffset.x == x && mThumbOffset.y == y) {
             return;
         }
-        mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight());
+        mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
+                mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
         mThumbOffset.set(x, y);
-        mInvalidateRect.union(new Rect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth,
-                mRv.getHeight()));
+        updateThumbPath();
+        mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
+                mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
         mRv.invalidate(mInvalidateRect);
     }
 
-    // Setter/getter for the search bar width for animations
-    public void setWidth(int width) {
-        mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight());
+    public Point getThumbOffset() {
+        return mThumbOffset;
+    }
+
+    // Setter/getter for the thumb bar width for animations
+    public void setThumbWidth(int width) {
+        mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
+                mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
         mThumbWidth = width;
-        mInvalidateRect.union(new Rect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth,
-                mRv.getHeight()));
+        updateThumbPath();
+        mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
+                mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
         mRv.invalidate(mInvalidateRect);
     }
 
-    public int getWidth() {
+    public int getThumbWidth() {
         return mThumbWidth;
     }
 
-    // Setter/getter for the track background alpha for animations
-    public void setTrackAlpha(int alpha) {
-        mTrackPaint.setAlpha(alpha);
-        mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight());
+    // Setter/getter for the track bar width for animations
+    public void setTrackWidth(int width) {
+        mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, 0, mThumbOffset.x + mThumbWidth,
+                mRv.getHeight());
+        mTrackWidth = width;
+        updateThumbPath();
+        mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, 0, mThumbOffset.x + mThumbWidth,
+                mRv.getHeight());
         mRv.invalidate(mInvalidateRect);
     }
 
-    public int getTrackAlpha() {
-        return mTrackPaint.getAlpha();
+    public int getTrackWidth() {
+        return mTrackWidth;
     }
 
     public int getThumbHeight() {
@@ -127,10 +157,18 @@
         return mThumbMaxWidth;
     }
 
-    public boolean isDragging() {
+    public float getLastTouchY() {
+        return mLastTouchY;
+    }
+
+    public boolean isDraggingThumb() {
         return mIsDragging;
     }
 
+    public boolean isThumbDetached() {
+        return mIsThumbDetached;
+    }
+
     /**
      * Handles the touch event and determines whether to show the fast scroller (or updates it if
      * it is already showing).
@@ -152,6 +190,9 @@
                         Math.abs(y - downY) > config.getScaledTouchSlop()) {
                     mRv.getParent().requestDisallowInterceptTouchEvent(true);
                     mIsDragging = true;
+                    if (mCanThumbDetach) {
+                        mIsThumbDetached = true;
+                    }
                     mTouchOffset += (lastY - downY);
                     mPopup.animateVisibility(true);
                     animateScrollbar(true);
@@ -166,11 +207,13 @@
                     mPopup.setSectionName(sectionName);
                     mPopup.animateVisibility(!sectionName.isEmpty());
                     mRv.invalidate(mPopup.updateFastScrollerBounds(mRv, lastY));
+                    mLastTouchY = boundedY;
                 }
                 break;
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
                 mTouchOffset = 0;
+                mLastTouchY = 0;
                 if (mIsDragging) {
                     mIsDragging = false;
                     mPopup.animateVisibility(false);
@@ -189,8 +232,7 @@
         if (mTrackPaint.getAlpha() > 0) {
             canvas.drawRect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight(), mTrackPaint);
         }
-        canvas.drawRect(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
-                mThumbOffset.y + mThumbHeight, mThumbPaint);
+        canvas.drawPath(mThumbPath, mThumbPaint);
 
         // Draw the popup
         mPopup.draw(canvas);
@@ -203,27 +245,46 @@
         if (mScrollbarAnimator != null) {
             mScrollbarAnimator.cancel();
         }
-        ObjectAnimator trackAlphaAnim = ObjectAnimator.ofInt(this, "trackAlpha",
-                isScrolling ? MAX_TRACK_ALPHA : 0);
-        ObjectAnimator thumbWidthAnim = ObjectAnimator.ofInt(this, "width",
-                isScrolling ? mThumbMaxWidth : mThumbMinWidth);
-        ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(),
-                mThumbPaint.getColor(), isScrolling ? mThumbActiveColor : mThumbInactiveColor);
-        colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animator) {
-                mThumbPaint.setColor((Integer) animator.getAnimatedValue());
-                mRv.invalidate(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
-                        mThumbOffset.y + mThumbHeight);
-            }
-        });
+
         mScrollbarAnimator = new AnimatorSet();
-        mScrollbarAnimator.playTogether(trackAlphaAnim, thumbWidthAnim, colorAnimation);
+        ObjectAnimator trackWidthAnim = ObjectAnimator.ofInt(this, "trackWidth",
+                isScrolling ? mThumbMaxWidth : mThumbMinWidth);
+        ObjectAnimator thumbWidthAnim = ObjectAnimator.ofInt(this, "thumbWidth",
+                isScrolling ? mThumbMaxWidth : mThumbMinWidth);
+        mScrollbarAnimator.playTogether(trackWidthAnim, thumbWidthAnim);
+        if (mThumbActiveColor != mThumbInactiveColor) {
+            ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(),
+                    mThumbPaint.getColor(), isScrolling ? mThumbActiveColor : mThumbInactiveColor);
+            colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animator) {
+                    mThumbPaint.setColor((Integer) animator.getAnimatedValue());
+                    mRv.invalidate(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
+                            mThumbOffset.y + mThumbHeight);
+                }
+            });
+            mScrollbarAnimator.play(colorAnimation);
+        }
         mScrollbarAnimator.setDuration(SCROLL_BAR_VIS_DURATION);
         mScrollbarAnimator.start();
     }
 
     /**
+     * Updates the path for the thumb drawable.
+     */
+    private void updateThumbPath() {
+        mThumbCurvature = mThumbMaxWidth - mThumbWidth;
+        mThumbPath.reset();
+        mThumbPath.moveTo(mThumbOffset.x + mThumbWidth, mThumbOffset.y);                    // tr
+        mThumbPath.lineTo(mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);     // br
+        mThumbPath.lineTo(mThumbOffset.x, mThumbOffset.y + mThumbHeight);                   // bl
+        mThumbPath.cubicTo(mThumbOffset.x, mThumbOffset.y + mThumbHeight,
+                mThumbOffset.x - mThumbCurvature, mThumbOffset.y + mThumbHeight / 2,
+                mThumbOffset.x, mThumbOffset.y);                                            // bl2tl
+        mThumbPath.close();
+    }
+
+    /**
      * Returns whether the specified points are near the scroll bar bounds.
      */
     private boolean isNearPoint(int x, int y) {
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index a0be8ea..5070878 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -34,11 +34,13 @@
 import android.util.TypedValue;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
+import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewParent;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.DecelerateInterpolator;
 import android.widget.TextView;
+
 import com.android.launcher3.IconCache.IconLoadRequest;
 import com.android.launcher3.model.PackageItemInfo;
 
@@ -508,7 +510,7 @@
             mIcon.setBounds(0, 0, iconSize, iconSize);
         }
         if (mLayoutHorizontal) {
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            if (Utilities.ATLEAST_JB_MR1) {
                 setCompoundDrawablesRelative(mIcon, null, null, null);
             } else {
                 setCompoundDrawables(mIcon, null, null, null);
@@ -538,6 +540,13 @@
             } else if (info instanceof ShortcutInfo) {
                 applyFromShortcutInfo((ShortcutInfo) info,
                         LauncherAppState.getInstance().getIconCache());
+                if ((info.rank < FolderIcon.NUM_ITEMS_IN_PREVIEW) && (info.container >= 0)) {
+                    View folderIcon =
+                            mLauncher.getWorkspace().getHomescreenIconByItemId(info.container);
+                    if (folderIcon != null) {
+                        folderIcon.invalidate();
+                    }
+                }
             } else if (info instanceof PackageItemInfo) {
                 applyFromPackageItemInfo((PackageItemInfo) info);
             }
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 574f229..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;
 
 /**
@@ -92,7 +95,7 @@
         // drawableLeft and drawableStart.
         mDrawable = getResources().getDrawable(resId);
 
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+        if (Utilities.ATLEAST_JB_MR1) {
             setCompoundDrawablesRelativeWithIntrinsicBounds(mDrawable, null, null, null);
         } else {
             setCompoundDrawablesWithIntrinsicBounds(mDrawable, null, null, null);
@@ -113,7 +116,7 @@
     @Override
     public final void onDragEnter(DragObject d) {
         d.dragView.setColor(mHoverColor);
-        if (Utilities.isLmpOrAbove()) {
+        if (Utilities.ATLEAST_LOLLIPOP) {
             animateTextColor(mHoverColor);
         } else {
             if (mCurrentFilter == null) {
@@ -130,8 +133,8 @@
         // Do nothing
     }
 
-	protected void resetHoverColor() {
-        if (Utilities.isLmpOrAbove()) {
+    protected void resetHoverColor() {
+        if (Utilities.ATLEAST_LOLLIPOP) {
             animateTextColor(mOriginalTextColor.getDefaultColor());
         } else {
             mDrawable.setColorFilter(null);
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index e96768f..772bb7b 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -18,7 +18,6 @@
 
 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;
@@ -54,8 +53,8 @@
 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.Thunk;
-import com.android.launcher3.widget.PendingAddWidgetInfo;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -212,6 +211,7 @@
 
         mBackground = (TransitionDrawable) res.getDrawable(R.drawable.bg_screenpanel);
         mBackground.setCallback(this);
+        mBackground.setAlpha((int) (mBackgroundAlpha * 255));
 
         mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE *
                 grid.iconSizePx);
@@ -405,7 +405,7 @@
         mIsDragTarget = false;
     }
 
-    boolean isDragTarget() {
+    public boolean isDragTarget() {
         return mIsDragTarget;
     }
 
@@ -415,13 +415,17 @@
             if (mIsDragOverlapping) {
                 mBackground.startTransition(BACKGROUND_ACTIVATE_DURATION);
             } else {
-                mBackground.reverseTransition(BACKGROUND_ACTIVATE_DURATION);
+                if (mBackgroundAlpha > 0f) {
+                    mBackground.reverseTransition(BACKGROUND_ACTIVATE_DURATION);
+                } else {
+                    mBackground.resetTransition();
+                }
             }
             invalidate();
         }
     }
 
-    boolean getIsDragOverlapping() {
+    public boolean getIsDragOverlapping() {
         return mIsDragOverlapping;
     }
 
@@ -444,12 +448,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);
             }
         }
 
@@ -564,7 +565,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.
@@ -1027,53 +1028,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();
         }
@@ -2155,17 +2160,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();
         }
     }
 
@@ -2678,65 +2680,6 @@
         resultRect.set(x, y, x + width, y + height);
     }
 
-    /**
-     * Computes the required horizontal and vertical cell spans to always
-     * fit the given rectangle.
-     *
-     * @param width Width in pixels
-     * @param height Height in pixels
-     * @param result An array of length 2 in which to store the result (may be null).
-     */
-    public static int[] rectToCell(Launcher launcher, int width, int height, int[] result) {
-        return rectToCell(launcher.getDeviceProfile(), launcher, width, height, result);
-    }
-
-    public static int[] rectToCell(DeviceProfile grid, Context context, int width, int height,
-            int[] result) {
-        Rect padding = grid.getWorkspacePadding(Utilities.isRtl(context.getResources()));
-
-        // Always assume we're working with the smallest span to make sure we
-        // reserve enough space in both orientations.
-        int parentWidth = DeviceProfile.calculateCellWidth(grid.widthPx
-                - padding.left - padding.right, (int) grid.inv.numColumns);
-        int parentHeight = DeviceProfile.calculateCellHeight(grid.heightPx
-                - padding.top - padding.bottom, (int) grid.inv.numRows);
-        int smallerSize = Math.min(parentWidth, parentHeight);
-
-        // Always round up to next largest cell
-        int spanX = (int) Math.ceil(width / (float) smallerSize);
-        int spanY = (int) Math.ceil(height / (float) smallerSize);
-
-        if (result == null) {
-            return new int[] { spanX, spanY };
-        }
-        result[0] = spanX;
-        result[1] = spanY;
-        return result;
-    }
-
-    /**
-     * Calculate the grid spans needed to fit given item
-     */
-    public void calculateSpans(ItemInfo info) {
-        final int minWidth;
-        final int minHeight;
-
-        if (info instanceof LauncherAppWidgetInfo) {
-            minWidth = ((LauncherAppWidgetInfo) info).minWidth;
-            minHeight = ((LauncherAppWidgetInfo) info).minHeight;
-        } else if (info instanceof PendingAddWidgetInfo) {
-            minWidth = ((PendingAddWidgetInfo) info).minWidth;
-            minHeight = ((PendingAddWidgetInfo) info).minHeight;
-        } else {
-            // It's not a widget, so it must be 1x1
-            info.spanX = info.spanY = 1;
-            return;
-        }
-        int[] spans = rectToCell(mLauncher, minWidth, minHeight, null);
-        info.spanX = spans[0];
-        info.spanY = spans[1];
-    }
-
     private void clearOccupiedCells() {
         for (int x = 0; x < mCountX; x++) {
             for (int y = 0; y < mCountY; y++) {
@@ -2859,10 +2802,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 ae6e3ba..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;
 
@@ -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 5966112..6364d90 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -56,7 +56,6 @@
     private final int overviewModeBarItemWidthPx;
     private final int overviewModeBarSpacerWidthPx;
     private final float overviewModeIconZoneRatio;
-    private final float overviewModeScaleFactor;
 
     // Workspace
     private int desiredWorkspaceLeftRightMarginPx;
@@ -136,8 +135,6 @@
                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_spacer_width);
         overviewModeIconZoneRatio =
                 res.getInteger(R.integer.config_dynamic_grid_overview_icon_zone_percentage) / 100f;
-        overviewModeScaleFactor =
-                res.getInteger(R.integer.config_dynamic_grid_overview_scale_percentage) / 100f;
         iconDrawablePaddingOriginalPx =
                 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
 
@@ -283,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);
@@ -334,18 +343,11 @@
         }
     }
 
-    Rect getOverviewModeButtonBarRect() {
+    int getOverviewModeButtonBarHeight() {
         int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx);
         zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx,
                 Math.max(overviewModeMinIconZoneHeightPx, zoneHeight));
-        return new Rect(0, availableHeightPx - zoneHeight, 0, availableHeightPx);
-    }
-
-    public float getOverviewModeScale(boolean isLayoutRtl) {
-        Rect workspacePadding = getWorkspacePadding(isLayoutRtl);
-        Rect overviewBar = getOverviewModeButtonBarRect();
-        int pageSpace = availableHeightPx - workspacePadding.top - workspacePadding.bottom;
-        return (overviewModeScaleFactor * (pageSpace - overviewBar.height())) / pageSpace;
+        return zoneHeight;
     }
 
     // The rect returned will be extended to below the system ui that covers the workspace
@@ -394,7 +396,7 @@
         final boolean isLayoutRtl = Utilities.isRtl(launcher.getResources());
 
         // Layout the search bar space
-        View searchBar = launcher.getSearchBar();
+        View searchBar = launcher.getSearchDropTargetBar();
         lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
         if (hasVerticalBarLayout) {
             // Vertical search bar space -- The search bar is fixed in the layout to be on the left
@@ -476,7 +478,7 @@
         // Layout the Overview Mode
         ViewGroup overviewMode = launcher.getOverviewPanel();
         if (overviewMode != null) {
-            Rect r = getOverviewModeButtonBarRect();
+            int overviewButtonBarHeight = getOverviewModeButtonBarHeight();
             lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
             lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
 
@@ -485,7 +487,7 @@
             int maxWidth = totalItemWidth + (visibleChildCount-1) * overviewModeBarSpacerWidthPx;
 
             lp.width = Math.min(availableWidthPx, maxWidth);
-            lp.height = r.height();
+            lp.height = overviewButtonBarHeight;
             overviewMode.setLayoutParams(lp);
 
             if (lp.width > totalItemWidth && visibleChildCount > 1) {
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index 3748464..592cd32 100644
--- a/src/com/android/launcher3/DropTarget.java
+++ b/src/com/android/launcher3/DropTarget.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3;
 
+import com.android.launcher3.dragndrop.DragView;
+
 import android.graphics.PointF;
 import android.graphics.Rect;
 
diff --git a/src/com/android/launcher3/allapps/AllAppsSearchEditView.java b/src/com/android/launcher3/ExtendedEditText.java
similarity index 67%
rename from src/com/android/launcher3/allapps/AllAppsSearchEditView.java
rename to src/com/android/launcher3/ExtendedEditText.java
index b7dcd66..bf4551b 100644
--- a/src/com/android/launcher3/allapps/AllAppsSearchEditView.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -13,37 +13,38 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.launcher3.allapps;
+package com.android.launcher3;
 
 import android.content.Context;
 import android.util.AttributeSet;
+import android.view.DragEvent;
 import android.view.KeyEvent;
 import android.widget.EditText;
 
 
 /**
- * The edit text for the search container
+ * The edit text that reports back when the back key has been pressed.
  */
-public class AllAppsSearchEditView extends EditText {
+public class ExtendedEditText extends EditText {
 
     /**
      * Implemented by listeners of the back key.
      */
     public interface OnBackKeyListener {
-        public void onBackKey();
+        public boolean onBackKey();
     }
 
     private OnBackKeyListener mBackKeyListener;
 
-    public AllAppsSearchEditView(Context context) {
-        this(context, null);
+    public ExtendedEditText(Context context) {
+        super(context);
     }
 
-    public AllAppsSearchEditView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
+    public ExtendedEditText(Context context, AttributeSet attrs) {
+        super(context, attrs);
     }
 
-    public AllAppsSearchEditView(Context context, AttributeSet attrs, int defStyleAttr) {
+    public ExtendedEditText(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
     }
 
@@ -56,10 +57,16 @@
         // If this is a back key, propagate the key back to the listener
         if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
             if (mBackKeyListener != null) {
-                mBackKeyListener.onBackKey();
+                return mBackKeyListener.onBackKey();
             }
             return false;
         }
         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 21f23b5..afb5fd8 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -52,11 +52,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;
 
@@ -124,7 +126,7 @@
 
     @Thunk FolderPagedView mContent;
     @Thunk View mContentWrapper;
-    FolderEditText mFolderName;
+    ExtendedEditText mFolderName;
 
     private View mFooter;
     private int mFooterHeight;
@@ -196,8 +198,15 @@
         mContent = (FolderPagedView) findViewById(R.id.folder_content);
         mContent.setFolder(this);
 
-        mFolderName = (FolderEditText) findViewById(R.id.folder_name);
-        mFolderName.setFolder(this);
+        mFolderName = (ExtendedEditText) findViewById(R.id.folder_name);
+        mFolderName.setOnBackKeyListener(new ExtendedEditText.OnBackKeyListener() {
+            @Override
+            public boolean onBackKey() {
+                // Close the activity on back key press
+                doneEditingFolderName(true);
+                return false;
+            }
+        });
         mFolderName.setOnFocusChangeListener(this);
 
         // We disable action mode for now since it messes up the view on phones
@@ -275,7 +284,7 @@
 
     @Override
     public void enableAccessibleDrag(boolean enable) {
-        mLauncher.getSearchBar().enableAccessibleDrag(enable);
+        mLauncher.getSearchDropTargetBar().enableAccessibleDrag(enable);
         for (int i = 0; i < mContent.getChildCount(); i++) {
             mContent.getPageAt(i).enableAccessibleDrag(enable, CellLayout.FOLDER_ACCESSIBILITY_DRAG);
         }
@@ -446,15 +455,11 @@
 
         Animator openFolderAnim = null;
         final Runnable onCompleteRunnable;
-        if (!Utilities.isLmpOrAbove()) {
+        if (!Utilities.ATLEAST_LOLLIPOP) {
             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;
 
@@ -477,8 +482,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);
@@ -557,11 +562,12 @@
             final boolean updateAnimationFlag = !mDragInProgress;
             openFolderAnim.addListener(new AnimatorListenerAdapter() {
 
+                @SuppressLint("InlinedApi")
                 @Override
                 public void onAnimationEnd(Animator animation) {
                     mFolderName.animate().setDuration(FOLDER_NAME_ANIMATION_DURATION)
                         .translationX(0)
-                        .setInterpolator(Utilities.isLmpOrAbove() ?
+                        .setInterpolator(Utilities.ATLEAST_LOLLIPOP ?
                                 AnimationUtils.loadInterpolator(mLauncher,
                                         android.R.interpolator.fast_out_slow_in)
                                 : new LogDecelerateInterpolator(100, 0));
@@ -623,12 +629,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) {
@@ -1389,7 +1390,7 @@
     }
 
     // Compares item position based on rank and position giving priority to the rank.
-    private static final Comparator<ItemInfo> ITEM_POS_COMPARATOR = new Comparator<ItemInfo>() {
+    public static final Comparator<ItemInfo> ITEM_POS_COMPARATOR = new Comparator<ItemInfo>() {
 
         @Override
         public int compare(ItemInfo lhs, ItemInfo rhs) {
diff --git a/src/com/android/launcher3/FolderEditText.java b/src/com/android/launcher3/FolderEditText.java
deleted file mode 100644
index c311008..0000000
--- a/src/com/android/launcher3/FolderEditText.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.android.launcher3;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.widget.EditText;
-
-public class FolderEditText extends EditText {
-
-    private Folder mFolder;
-
-    public FolderEditText(Context context) {
-        super(context);
-    }
-
-    public FolderEditText(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public FolderEditText(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-    }
-
-    public void setFolder(Folder folder) {
-        mFolder = folder;
-    }
-
-    @Override
-    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
-        // Catch the back button on the soft keyboard so that we can just close the activity
-        if (event.getKeyCode() == android.view.KeyEvent.KEYCODE_BACK) {
-            mFolder.doneEditingFolderName(true);
-        }
-        return super.onKeyPreIme(keyCode, event);
-    }
-}
diff --git a/src/com/android/launcher3/FolderIcon.java b/src/com/android/launcher3/FolderIcon.java
index de0df0d..a175d86 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;
diff --git a/src/com/android/launcher3/FolderPagedView.java b/src/com/android/launcher3/FolderPagedView.java
index f2ec1b6..d8ef34e 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;
@@ -329,6 +330,10 @@
                 lp.cellY = info.cellY;
                 currentPage.addViewToCellLayout(
                         v, -1, mFolder.mLauncher.getViewIdForItem(info), lp, true);
+
+                if (rank < FolderIcon.NUM_ITEMS_IN_PREVIEW && v instanceof BubbleTextView) {
+                    ((BubbleTextView) v).verifyHighRes();
+                }
             }
 
             rank ++;
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/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index 4bc5019..6d5bbab 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -134,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);
@@ -153,6 +146,27 @@
         values.put(LauncherSettings.Favorites.SPANX, spanX);
         values.put(LauncherSettings.Favorites.SPANY, spanY);
         values.put(LauncherSettings.Favorites.RANK, rank);
+    }
+
+    public void readFromValues(ContentValues values) {
+        itemType = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE);
+        container = values.getAsLong(LauncherSettings.Favorites.CONTAINER);
+        screenId = values.getAsLong(LauncherSettings.Favorites.SCREEN);
+        cellX = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE);
+        cellY = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE);
+        spanX = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE);
+        spanY = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE);
+        rank = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE);
+    }
+
+    /**
+     * Write the fields of this item to the DB
+     *
+     * @param context A context object to use for getting UserManagerCompat
+     * @param values
+     */
+    void onAddToDatabase(Context context, ContentValues values) {
+        writeToValues(values);
         long serialNumber = UserManagerCompat.getInstance(context).getSerialNumberForUser(user);
         values.put(LauncherSettings.Favorites.PROFILE_ID, serialNumber);
 
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 279a82b..d926cc1 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;
@@ -116,8 +119,6 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
 import java.text.DateFormat;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -132,9 +133,8 @@
  */
 public class Launcher extends Activity
         implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
-                   View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener,
-                   LauncherStateTransitionAnimation.Callbacks {
-    static final String TAG = "Launcher";
+                   View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener {
+    public static final String TAG = "Launcher";
     static final boolean LOGD = false;
 
     static final boolean PROFILE_STARTUP = false;
@@ -177,18 +177,8 @@
     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
@@ -239,8 +229,6 @@
     private final BroadcastReceiver mCloseSystemDialogsReceiver
             = new CloseSystemDialogsIntentReceiver();
 
-    private LayoutInflater mInflater;
-
     @Thunk Workspace mWorkspace;
     private View mLauncherView;
     private View mPageIndicators;
@@ -272,7 +260,6 @@
     @Thunk WidgetsContainerView mWidgetsView;
     @Thunk WidgetsModel mWidgetsModel;
 
-    private boolean mAutoAdvanceRunning = false;
     private AppWidgetHostView mQsb;
 
     private Bundle mSavedState;
@@ -306,16 +293,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;
 
@@ -357,18 +345,6 @@
         }
     }
 
-    // TODO: remove this field and call method directly when Launcher3 can depend on M APIs
-    private static Method sClipRevealMethod = null;
-    static {
-        Class<?> activityOptionsClass = ActivityOptions.class;
-        try {
-            sClipRevealMethod = activityOptionsClass.getDeclaredMethod("makeClipRevealAnimation",
-                    View.class, int.class, int.class, int.class, int.class);
-        } catch (Exception e) {
-            // Earlier version
-        }
-    }
-
     @Thunk Runnable mBuildLayersRunnable = new Runnable() {
         public void run() {
             if (mWorkspace != null) {
@@ -447,8 +423,7 @@
         mIconCache = app.getIconCache();
 
         mDragController = new DragController(this);
-        mInflater = getLayoutInflater();
-        mStateTransitionAnimation = new LauncherStateTransitionAnimation(this, this);
+        mStateTransitionAnimation = new LauncherStateTransitionAnimation(this);
 
         mStats = new Stats(this);
 
@@ -652,10 +627,6 @@
         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().
@@ -664,7 +635,7 @@
 
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
     public static int generateViewId() {
-        if (Build.VERSION.SDK_INT >= 17) {
+        if (Utilities.ATLEAST_JB_MR1) {
             return View.generateViewId();
         } else {
             // View.generateViewId() is not available. The following fallback logic is a copy
@@ -739,6 +710,7 @@
         };
 
         if (requestCode == REQUEST_BIND_APPWIDGET) {
+            // This is called only if the user did not previously have permissions to bind widgets
             final int appWidgetId = data != null ?
                     data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
             if (resultCode == RESULT_CANCELED) {
@@ -748,6 +720,10 @@
             } else if (resultCode == RESULT_OK) {
                 addAppWidgetImpl(appWidgetId, mPendingAddInfo, null,
                         mPendingAddWidgetInfo, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
+
+                // When the user has granted permission to bind widgets, we should check to see if
+                // we can inflate the default search bar widget.
+                getOrCreateQsbBar();
             }
             return;
         } else if (requestCode == REQUEST_PICK_WALLPAPER) {
@@ -1314,16 +1290,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 ?
@@ -1482,7 +1451,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);
@@ -1542,23 +1511,6 @@
         }
     }
 
-    private int[] getSpanForWidget(ComponentName component, int minWidth, int minHeight) {
-        Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(this, component, null);
-        // We want to account for the extra amount of padding that we are adding to the widget
-        // to ensure that it gets the full amount of space that it has requested
-        int requiredWidth = minWidth + padding.left + padding.right;
-        int requiredHeight = minHeight + padding.top + padding.bottom;
-        return CellLayout.rectToCell(this, requiredWidth, requiredHeight, null);
-    }
-
-    public int[] getSpanForWidget(AppWidgetProviderInfo info) {
-        return getSpanForWidget(info.provider, info.minWidth, info.minHeight);
-    }
-
-    public int[] getMinSpanForWidget(AppWidgetProviderInfo info) {
-        return getSpanForWidget(info.provider, info.minResizeWidth, info.minResizeHeight);
-    }
-
     /**
      * Add a widget to the workspace.
      *
@@ -1655,18 +1607,18 @@
         }
         registerReceiver(mReceiver, filter);
         FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
-        setupTransparentSystemBarsForLmp();
+        setupTransparentSystemBarsForLollipop();
         mAttached = true;
         mVisible = true;
     }
 
     /**
-     * Sets up transparent navigation and status bars in LMP.
+     * Sets up transparent navigation and status bars in Lollipop.
      * This method is a no-op for other platform versions.
      */
     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
-    private void setupTransparentSystemBarsForLmp() {
-        if (Utilities.isLmpOrAbove()) {
+    private void setupTransparentSystemBarsForLollipop() {
+        if (Utilities.ATLEAST_LOLLIPOP) {
             Window window = getWindow();
             window.getAttributes().systemUiVisibility |=
                     (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
@@ -1745,11 +1697,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);
@@ -1766,7 +1718,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() {
@@ -1776,7 +1728,7 @@
                     }
                     i++;
                 }
-                sendAdvanceMessage(mAdvanceInterval);
+                sendAdvanceMessage(ADVANCE_INTERVAL);
             }
             return true;
         }
@@ -1833,7 +1785,7 @@
         return mOverviewPanel;
     }
 
-    public SearchDropTargetBar getSearchBar() {
+    public SearchDropTargetBar getSearchDropTargetBar() {
         return mSearchDropTargetBar;
     }
 
@@ -1869,29 +1821,22 @@
         super.onNewIntent(intent);
 
         // Close the menu
-        if (Intent.ACTION_MAIN.equals(intent.getAction())) {
+        Folder openFolder = mWorkspace.getOpenFolder();
+        boolean alreadyOnHome = mHasFocus && ((intent.getFlags() &
+                Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
+                != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
+        boolean isActionMain = Intent.ACTION_MAIN.equals(intent.getAction());
+        if (isActionMain) {
             // also will cancel mWaitingForResult.
             closeSystemDialogs();
 
-            final boolean alreadyOnHome = mHasFocus && ((intent.getFlags() &
-                    Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
-                    != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
-
             if (mWorkspace == null) {
                 // Can be cases where mWorkspace is null, this prevents a NPE
                 return;
             }
-            Folder openFolder = mWorkspace.getOpenFolder();
             // In all these cases, only animate if we're already on home
             mWorkspace.exitWidgetResizeMode();
 
-            boolean moveToDefaultScreen = mLauncherCallbacks != null ?
-                    mLauncherCallbacks.shouldMoveToDefaultScreenOnHomeIntent() : true;
-            if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() &&
-                    openFolder == null && moveToDefaultScreen) {
-                mWorkspace.moveToDefaultScreen(true);
-            }
-
             closeFolder();
             exitSpringLoadedDragMode();
 
@@ -1925,13 +1870,30 @@
             }
         }
 
-        if (DEBUG_RESUME_TIME) {
-            Log.d(TAG, "Time spent in onNewIntent: " + (System.currentTimeMillis() - startTime));
-        }
-
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onNewIntent(intent);
         }
+
+        // Defer moving to the default screen until after we callback to the LauncherCallbacks
+        // 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() {
+                    @Override
+                    public void run() {
+                        mWorkspace.moveToDefaultScreen(true);
+                    }
+                });
+            }
+        }
+
+        if (DEBUG_RESUME_TIME) {
+            Log.d(TAG, "Time spent in onNewIntent: " + (System.currentTimeMillis() - startTime));
+        }
     }
 
     @Override
@@ -1957,12 +1919,9 @@
 
         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);
         }
@@ -2139,6 +2098,15 @@
         }
     }
 
+    public void startSearchFromAllApps(View v, Intent searchIntent, String searchQuery) {
+        if (mLauncherCallbacks != null && mLauncherCallbacks.startSearchFromAllApps(searchQuery)) {
+            return;
+        }
+
+        // If not handled, then just start the provided search intent
+        startActivitySafely(v, searchIntent, null);
+    }
+
     public boolean isOnCustomContent() {
         return mWorkspace.isOnOrMovingToCustomContent();
     }
@@ -2207,7 +2175,7 @@
         mPendingAddInfo.screenId = -1;
         mPendingAddInfo.cellX = mPendingAddInfo.cellY = -1;
         mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1;
-        mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = -1;
+        mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = 1;
     }
 
     void addAppWidgetImpl(final int appWidgetId, final ItemInfo info, final
@@ -2539,6 +2507,10 @@
         if (!isAppsViewVisible()) {
             showAppsView(true /* animated */, false /* resetListToTop */,
                     true /* updatePredictedApps */, false /* focusSearchBar */);
+
+            if (mLauncherCallbacks != null) {
+                mLauncherCallbacks.onClickAllAppsButton(v);
+            }
         }
     }
 
@@ -2667,6 +2639,7 @@
             throw new IllegalArgumentException("Input must be a FolderIcon");
         }
 
+        // TODO(sunnygoyal): Re-evaluate this code.
         FolderIcon folderIcon = (FolderIcon) v;
         final FolderInfo info = folderIcon.getFolderInfo();
         Folder openFolder = mWorkspace.getFolderForTag(info);
@@ -2870,8 +2843,7 @@
             Bundle optsBundle = null;
             if (useLaunchAnimation) {
                 ActivityOptions opts = null;
-                if (sClipRevealMethod != null) {
-                    // TODO: call method directly when Launcher3 can depend on M APIs
+                if (Utilities.ATLEAST_MARSHMALLOW) {
                     int left = 0, top = 0;
                     int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
                     if (v instanceof TextView) {
@@ -2885,22 +2857,12 @@
                             height = bounds.height();
                         }
                     }
-                    try {
-                        opts = (ActivityOptions) sClipRevealMethod.invoke(null, v,
-                                left, top, width, height);
-                    } catch (IllegalAccessException e) {
-                        Log.d(TAG, "Could not call makeClipRevealAnimation: " + e);
-                        sClipRevealMethod = null;
-                    } catch (InvocationTargetException e) {
-                        Log.d(TAG, "Could not call makeClipRevealAnimation: " + e);
-                        sClipRevealMethod = null;
-                    }
-                }
-                if (opts == null && !Utilities.isLmpOrAbove()) {
+                    opts = ActivityOptions.makeClipRevealAnimation(v, left, top, width, height);
+                } else if (!Utilities.ATLEAST_LOLLIPOP) {
                     // Below L, we use a scale up animation
                     opts = ActivityOptions.makeScaleUpAnimation(v, 0, 0,
                                     v.getMeasuredWidth(), v.getMeasuredHeight());
-                } else if (opts == null && Utilities.isLmpMR1()) {
+                } else if (Utilities.ATLEAST_LOLLIPOP_MR1) {
                     // On L devices, we use the device default slide-up transition.
                     // On L MR1 devices, we a custom version of the slide-up transition which
                     // doesn't have the delay present in the device default.
@@ -2929,7 +2891,7 @@
         return false;
     }
 
-    @Thunk boolean startActivitySafely(View v, Intent intent, Object tag) {
+    public boolean startActivitySafely(View v, Intent intent, Object tag) {
         boolean success = false;
         if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) {
             Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
@@ -2998,10 +2960,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();
@@ -3013,9 +2971,9 @@
         copyFolderIconToImage(fi);
         fi.setVisibility(View.INVISIBLE);
 
-        ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
-                scaleX, scaleY);
-        if (Utilities.isLmpOrAbove()) {
+        ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(
+                mFolderIconImageView, 0, 1.5f, 1.5f);
+        if (Utilities.ATLEAST_LOLLIPOP) {
             oa.setInterpolator(new LogDecelerateInterpolator(100, 0));
         }
         oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
@@ -3024,17 +2982,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
@@ -3253,14 +3206,6 @@
         }
     }
 
-    @Override
-    public void onStateTransitionHideSearchBar() {
-        // Hide the search bar
-        if (mSearchDropTargetBar != null) {
-            mSearchDropTargetBar.hideSearchBar(false /* animated */);
-        }
-    }
-
     public void showWorkspace(boolean animated) {
         showWorkspace(animated, null);
     }
@@ -3269,16 +3214,9 @@
         boolean changed = mState != State.WORKSPACE ||
                 mWorkspace.getState() != Workspace.State.NORMAL;
         if (changed) {
-            boolean wasInSpringLoadedMode = (mState != State.WORKSPACE);
             mWorkspace.setVisibility(View.VISIBLE);
-            mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.NORMAL,
-                    animated, onCompleteRunnable);
-
-            // Show the search bar (only animate if we were showing the drop target bar in spring
-            // loaded mode)
-            if (mSearchDropTargetBar != null) {
-                mSearchDropTargetBar.showSearchBar(animated && wasInSpringLoadedMode);
-            }
+            mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
+                    Workspace.State.NORMAL, animated, onCompleteRunnable);
 
             // Set focus to the AppsCustomize button
             if (mAllAppsButton != null) {
@@ -3302,8 +3240,8 @@
 
     void showOverviewMode(boolean animated) {
         mWorkspace.setVisibility(View.VISIBLE);
-        mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.OVERVIEW,
-                animated, null /* onCompleteRunnable */);
+        mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
+                Workspace.State.OVERVIEW, animated, null /* onCompleteRunnable */);
         mState = State.WORKSPACE;
     }
 
@@ -3356,9 +3294,10 @@
         }
 
         if (toState == State.APPS) {
-            mStateTransitionAnimation.startAnimationToAllApps(animated, focusSearchBar);
+            mStateTransitionAnimation.startAnimationToAllApps(mWorkspace.getState(), animated,
+                    focusSearchBar);
         } else {
-            mStateTransitionAnimation.startAnimationToWidgets(animated);
+            mStateTransitionAnimation.startAnimationToWidgets(mWorkspace.getState(), animated);
         }
 
         // Change the state *after* we've called all the transition code
@@ -3380,14 +3319,25 @@
      * new state.
      */
     public Animator startWorkspaceStateChangeAnimation(Workspace.State toState,
-            boolean animated, boolean hasOverlaySearchBar, HashMap<View, Integer> layerViews) {
+            boolean animated, HashMap<View, Integer> layerViews) {
         Workspace.State fromState = mWorkspace.getState();
-        Animator anim = mWorkspace.setStateWithAnimation(
-                toState, animated, hasOverlaySearchBar, 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 ||
@@ -3395,8 +3345,9 @@
             return;
         }
 
-        mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.SPRING_LOADED,
-                true /* animated */, null /* onCompleteRunnable */);
+        mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
+                Workspace.State.SPRING_LOADED, true /* animated */,
+                null /* onCompleteRunnable */);
         mState = isAppsViewVisible() ? State.APPS_SPRING_LOADED : State.WIDGETS_SPRING_LOADED;
     }
 
@@ -3775,7 +3726,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);
@@ -3878,7 +3829,8 @@
 
         if (!mIsSafeModeEnabled
                 && ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0)
-                && ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) != 0)) {
+                && (item.restoreStatus != LauncherAppWidgetInfo.RESTORE_COMPLETED)) {
+
             if (appWidgetInfo == null) {
                 if (DEBUG_WIDGETS) {
                     Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
@@ -3888,42 +3840,51 @@
                 LauncherModel.deleteItemFromDatabase(this, item);
                 return;
             }
-            // Note: This assumes that the id remap broadcast is received before this step.
-            // If that is not the case, the id remap will be ignored and user may see the
-            // click to setup view.
-            PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(this, appWidgetInfo, null);
-            pendingInfo.spanX = item.spanX;
-            pendingInfo.spanY = item.spanY;
-            pendingInfo.minSpanX = item.minSpanX;
-            pendingInfo.minSpanY = item.minSpanY;
-            Bundle options = null;
-                    WidgetHostViewLoader.getDefaultOptionsForWidget(this, pendingInfo);
 
-            int newWidgetId = mAppWidgetHost.allocateAppWidgetId();
-            boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
-                    newWidgetId, appWidgetInfo, options);
+            // If we do not have a valid id, try to bind an id.
+            if ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) != 0) {
+                // Note: This assumes that the id remap broadcast is received before this step.
+                // If that is not the case, the id remap will be ignored and user may see the
+                // click to setup view.
+                PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(this, appWidgetInfo, null);
+                pendingInfo.spanX = item.spanX;
+                pendingInfo.spanY = item.spanY;
+                pendingInfo.minSpanX = item.minSpanX;
+                pendingInfo.minSpanY = item.minSpanY;
+                Bundle options = null;
+                        WidgetHostViewLoader.getDefaultOptionsForWidget(this, pendingInfo);
 
-            // TODO consider showing a permission dialog when the widget is clicked.
-            if (!success) {
-                mAppWidgetHost.deleteAppWidgetId(newWidgetId);
-                if (DEBUG_WIDGETS) {
-                    Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
-                            + " belongs to component " + item.providerName
-                            + ", as the launcher is unable to bing a new widget id");
+                int newWidgetId = mAppWidgetHost.allocateAppWidgetId();
+                boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
+                        newWidgetId, appWidgetInfo, options);
+
+                // TODO consider showing a permission dialog when the widget is clicked.
+                if (!success) {
+                    mAppWidgetHost.deleteAppWidgetId(newWidgetId);
+                    if (DEBUG_WIDGETS) {
+                        Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
+                                + " belongs to component " + item.providerName
+                                + ", as the launcher is unable to bing a new widget id");
+                    }
+                    LauncherModel.deleteItemFromDatabase(this, item);
+                    return;
                 }
-                LauncherModel.deleteItemFromDatabase(this, item);
-                return;
+
+                item.appWidgetId = newWidgetId;
+
+                // If the widget has a configure activity, it is still needs to set it up, otherwise
+                // the widget is ready to go.
+                item.restoreStatus = (appWidgetInfo.configure == null)
+                        ? LauncherAppWidgetInfo.RESTORE_COMPLETED
+                        : LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
+
+                LauncherModel.updateItemInDatabase(this, item);
+            } else if (((item.restoreStatus & LauncherAppWidgetInfo.FLAG_UI_NOT_READY) != 0)
+                    && (appWidgetInfo.configure == null)) {
+                // If the ID is already valid, verify if we need to configure or not.
+                item.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
+                LauncherModel.updateItemInDatabase(this, item);
             }
-
-            item.appWidgetId = newWidgetId;
-
-            // If the widget has a configure activity, it is still needs to set it up, otherwise
-            // the widget is ready to go.
-            item.restoreStatus = (appWidgetInfo.configure == null)
-                    ? LauncherAppWidgetInfo.RESTORE_COMPLETED
-                    : LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
-
-            LauncherModel.updateItemInDatabase(this, item);
         }
 
         if (!mIsSafeModeEnabled && item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
@@ -3934,6 +3895,8 @@
             }
 
             item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
+            item.minSpanX = appWidgetInfo.minSpanX;
+            item.minSpanY = appWidgetInfo.minSpanY;
         } else {
             appWidgetInfo = null;
             PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item,
@@ -4060,10 +4023,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));
@@ -4078,7 +4038,7 @@
         return mDeviceProfile.getSearchBarBounds(Utilities.isRtl(getResources()));
     }
 
-    public void bindSearchablesChanged() {
+    public void bindSearchProviderChanged() {
         if (mSearchDropTargetBar == null) {
             return;
         }
@@ -4301,13 +4261,14 @@
         return oriMap[(d.getRotation() + indexOffset) % 4];
     }
 
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
     public void lockScreenOrientation() {
         if (mRotationEnabled) {
-            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
+            if (Utilities.ATLEAST_JB_MR2) {
+                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
+            } else {
                 setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources()
                         .getConfiguration().orientation));
-            } else {
-                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
             }
         }
     }
@@ -4321,7 +4282,7 @@
                     public void run() {
                         setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
                     }
-                }, mRestoreScreenOrientationDelay);
+                }, RESTORE_SCREEN_ORIENTATION_DELAY);
             }
         }
     }
@@ -4494,14 +4455,16 @@
         if (mWorkspace != null) mWorkspace.setAlpha(1f);
         if (mHotseat != null) mHotseat.setAlpha(1f);
         if (mPageIndicators != null) mPageIndicators.setAlpha(1f);
-        if (mSearchDropTargetBar != null) mSearchDropTargetBar.showSearchBar(false);
+        if (mSearchDropTargetBar != null) mSearchDropTargetBar.animateToState(
+                SearchDropTargetBar.State.SEARCH_BAR, 0);
     }
 
     void hideWorkspaceSearchAndHotseat() {
         if (mWorkspace != null) mWorkspace.setAlpha(0f);
         if (mHotseat != null) mHotseat.setAlpha(0f);
         if (mPageIndicators != null) mPageIndicators.setAlpha(0f);
-        if (mSearchDropTargetBar != null) mSearchDropTargetBar.hideSearchBar(false);
+        if (mSearchDropTargetBar != null) mSearchDropTargetBar.animateToState(
+                SearchDropTargetBar.State.INVISIBLE, 0);
     }
 
     // TODO: These method should be a part of LauncherSearchCallback
@@ -4509,7 +4472,7 @@
     public ItemInfo createAppDragInfo(Intent appLaunchIntent) {
         // Called from search suggestion
         UserHandleCompat user = null;
-        if (Utilities.isLmpOrAbove()) {
+        if (Utilities.ATLEAST_LOLLIPOP) {
             UserHandle userHandle = appLaunchIntent.getParcelableExtra(Intent.EXTRA_USER);
             if (userHandle != null) {
                 user = UserHandleCompat.fromUser(userHandle);
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 0b7b1fd..5a7fadb 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -17,7 +17,7 @@
 package com.android.launcher3;
 
 import android.app.SearchManager;
-import android.content.ComponentName;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -26,6 +26,7 @@
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.PackageInstallerCompat;
+import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.util.Thunk;
 
 import java.lang.ref.WeakReference;
@@ -33,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;
@@ -87,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);
@@ -96,12 +95,22 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_LOCALE_CHANGED);
         filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
-        filter.addAction(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED);
         // For handling managed profiles
         filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED);
         filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED);
 
         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));
+        }
     }
 
     /**
@@ -126,7 +135,7 @@
     LauncherModel setLauncher(Launcher launcher) {
         getLauncherProvider().setLauncherProviderChangeListener(launcher);
         mModel.initialize(launcher);
-        mAccessibilityDelegate = ((launcher != null) && Utilities.isLmpOrAbove()) ?
+        mAccessibilityDelegate = ((launcher != null) && Utilities.ATLEAST_LOLLIPOP) ?
             new LauncherAccessibilityDelegate(launcher) : null;
         return mModel;
     }
@@ -147,7 +156,7 @@
         sLauncherProvider = new WeakReference<LauncherProvider>(provider);
     }
 
-    static LauncherProvider getLauncherProvider() {
+    public static LauncherProvider getLauncherProvider() {
         return sLauncherProvider.get();
     }
 
@@ -158,10 +167,6 @@
     public WidgetPreviewLoader getWidgetCache() {
         return mWidgetCache;
     }
-    
-    public void onWallpaperChanged() {
-        mWallpaperChangedSinceLastCheck = true;
-    }
 
     public boolean hasWallpaperChangedSinceLastCheck() {
         boolean result = mWallpaperChangedSinceLastCheck;
@@ -172,8 +177,4 @@
     public InvariantDeviceProfile getInvariantDeviceProfile() {
         return mInvariantDeviceProfile;
     }
-
-    public static boolean isDogfoodBuild() {
-        return getInstance().mBuildInfo.isDogfoodBuild();
-    }
 }
diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java
index de7c610..6c3a1e8 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHost.java
@@ -95,8 +95,6 @@
     }
 
     protected void onProvidersChanged() {
-        mLauncher.getModel().loadAndBindWidgetsAndShortcuts(mLauncher, mLauncher,
-                true /* refresh */);
         if (!mProviderChangeListeners.isEmpty()) {
             for (Runnable callback : new ArrayList<>(mProviderChangeListeners)) {
                 callback.run();
diff --git a/src/com/android/launcher3/LauncherAppWidgetHostView.java b/src/com/android/launcher3/LauncherAppWidgetHostView.java
index cf461a5..d8cb682 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}
diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java
index aad18b5..882f7e2 100644
--- a/src/com/android/launcher3/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java
@@ -68,10 +68,6 @@
 
     ComponentName providerName;
 
-    // TODO: Are these necessary here?
-    int minWidth = -1;
-    int minHeight = -1;
-
     /**
      * Indicates the restore status of the widget.
      */
diff --git a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
index 85af92f..71a08a8 100644
--- a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
@@ -1,10 +1,13 @@
 package com.android.launcher3;
 
 import android.annotation.TargetApi;
+import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.Parcel;
@@ -19,10 +22,10 @@
 
     public boolean isCustomWidget = false;
 
-    private int mSpanX = -1;
-    private int mSpanY = -1;
-    private int mMinSpanX = -1;
-    private int mMinSpanY = -1;
+    public int spanX;
+    public int spanY;
+    public int minSpanX;
+    public int minSpanY;
 
     public static LauncherAppWidgetProviderInfo fromProviderInfo(Context context,
             AppWidgetProviderInfo info) {
@@ -41,6 +44,7 @@
 
     public LauncherAppWidgetProviderInfo(Parcel in) {
         super(in);
+        initSpans();
     }
 
     public LauncherAppWidgetProviderInfo(Context context, CustomAppWidget widget) {
@@ -52,6 +56,41 @@
         previewImage = widget.getPreviewImage();
         initialLayout = widget.getWidgetLayout();
         resizeMode = widget.getResizeMode();
+        initSpans();
+    }
+
+    private void initSpans() {
+        LauncherAppState app = LauncherAppState.getInstance();
+        InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
+
+        // We only care out the cell size, which is independent of the the layout direction.
+        Rect paddingLand = idp.landscapeProfile.getWorkspacePadding(false /* isLayoutRtl */);
+        Rect paddingPort = idp.portraitProfile.getWorkspacePadding(false /* isLayoutRtl */);
+
+        // Always assume we're working with the smallest span to make sure we
+        // reserve enough space in both orientations.
+        float smallestCellWidth = DeviceProfile.calculateCellWidth(Math.min(
+                idp.landscapeProfile.widthPx - paddingLand.left - paddingLand.right,
+                idp.portraitProfile.widthPx - paddingPort.left - paddingPort.right),
+                idp.numColumns);
+        float smallestCellHeight = DeviceProfile.calculateCellWidth(Math.min(
+                idp.landscapeProfile.heightPx - paddingLand.top - paddingLand.bottom,
+                idp.portraitProfile.heightPx - paddingPort.top - paddingPort.bottom),
+                idp.numRows);
+
+        // We want to account for the extra amount of padding that we are adding to the widget
+        // to ensure that it gets the full amount of space that it has requested.
+        Rect widgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(
+                app.getContext(), provider, null);
+        spanX = Math.max(1, (int) Math.ceil(
+                        (minWidth + widgetPadding.left + widgetPadding.right) / smallestCellWidth));
+        spanY = Math.max(1, (int) Math.ceil(
+                (minHeight + widgetPadding.top + widgetPadding.bottom) / smallestCellHeight));
+
+        minSpanX = Math.max(1, (int) Math.ceil(
+                (minResizeWidth + widgetPadding.left + widgetPadding.right) / smallestCellWidth));
+        minSpanY = Math.max(1, (int) Math.ceil(
+                (minResizeHeight + widgetPadding.top + widgetPadding.bottom) / smallestCellHeight));
     }
 
     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
@@ -79,35 +118,9 @@
                 provider.toString(), provider.getPackageName(), provider.getShortClassName(), getLabel(pm));
     }
 
-    public int getSpanX(Launcher launcher) {
-        lazyLoadSpans(launcher);
-        return mSpanX;
-    }
-
-    public int getSpanY(Launcher launcher) {
-        lazyLoadSpans(launcher);
-        return mSpanY;
-    }
-
-    public int getMinSpanX(Launcher launcher) {
-        lazyLoadSpans(launcher);
-        return mMinSpanX;
-    }
-
-    public int getMinSpanY(Launcher launcher) {
-        lazyLoadSpans(launcher);
-        return mMinSpanY;
-    }
-
-    private void lazyLoadSpans(Launcher launcher) {
-        if (mSpanX < 0 || mSpanY < 0 || mMinSpanX < 0 || mMinSpanY < 0) {
-            int[] minResizeSpan = launcher.getMinSpanForWidget(this);
-            int[] span = launcher.getSpanForWidget(this);
-
-            mSpanX = span[0];
-            mSpanY = span[1];
-            mMinSpanX = minResizeSpan[0];
-            mMinSpanY = minResizeSpan[1];
-        }
+    public Point getMinSpans(InvariantDeviceProfile idp, Context context) {
+        return new Point(
+                (resizeMode & RESIZE_HORIZONTAL) != 0 ? minSpanX : -1,
+                        (resizeMode & RESIZE_VERTICAL) != 0 ? minSpanY : -1);
     }
  }
diff --git a/src/com/android/launcher3/LauncherBackupAgentHelper.java b/src/com/android/launcher3/LauncherBackupAgentHelper.java
index a92a889..8eb4e63 100644
--- a/src/com/android/launcher3/LauncherBackupAgentHelper.java
+++ b/src/com/android/launcher3/LauncherBackupAgentHelper.java
@@ -24,6 +24,8 @@
 import android.os.ParcelFileDescriptor;
 import android.util.Log;
 
+import com.android.launcher3.model.MigrateFromRestoreTask;
+
 import java.io.IOException;
 
 public class LauncherBackupAgentHelper extends BackupAgentHelper {
@@ -63,7 +65,7 @@
     @Override
     public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
             throws IOException {
-        if (!Utilities.isLmpOrAbove()) {
+        if (!Utilities.ATLEAST_LOLLIPOP) {
             // No restore for old devices.
             Log.i(TAG, "You shall not pass!!!");
             Log.d(TAG, "Restore is only supported on devices running Lollipop and above.");
@@ -91,11 +93,19 @@
             LauncherAppState.getLauncherProvider().clearFlagEmptyDbCreated();
             LauncherClings.synchonouslyMarkFirstRunClingDismissed(this);
 
-            // TODO: Update the backup set to include rank.
+            // Rank was added in v4.
             if (mHelper.restoredBackupVersion <= 3) {
                 LauncherAppState.getLauncherProvider().updateFolderItemsRank();
-                LauncherAppState.getLauncherProvider().convertShortcutsToLauncherActivities();
             }
+
+            if (MigrateFromRestoreTask.ENABLED && mHelper.shouldAttemptWorkspaceMigration()) {
+                MigrateFromRestoreTask.markForMigration(getApplicationContext(),
+                        (int) mHelper.migrationCompatibleProfileData.desktopCols,
+                        (int) mHelper.migrationCompatibleProfileData.desktopRows,
+                        mHelper.widgetSizes);
+            }
+
+            LauncherAppState.getLauncherProvider().convertShortcutsToLauncherActivities();
         } else {
             if (VERBOSE) Log.v(TAG, "Nothing was restored, clearing DB");
             LauncherAppState.getLauncherProvider().createEmptyDB();
diff --git a/src/com/android/launcher3/LauncherBackupHelper.java b/src/com/android/launcher3/LauncherBackupHelper.java
index 38a4bde..2d11d3a 100644
--- a/src/com/android/launcher3/LauncherBackupHelper.java
+++ b/src/com/android/launcher3/LauncherBackupHelper.java
@@ -32,6 +32,7 @@
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.graphics.Point;
 import android.graphics.drawable.Drawable;
 import android.os.ParcelFileDescriptor;
 import android.text.TextUtils;
@@ -51,6 +52,7 @@
 import com.android.launcher3.backup.BackupProtos.Widget;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.model.MigrateFromRestoreTask;
 import com.android.launcher3.util.Thunk;
 import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
 import com.google.protobuf.nano.MessageNano;
@@ -75,7 +77,7 @@
     private static final boolean VERBOSE = LauncherBackupAgentHelper.VERBOSE;
     private static final boolean DEBUG = LauncherBackupAgentHelper.DEBUG;
 
-    private static final int BACKUP_VERSION = 3;
+    private static final int BACKUP_VERSION = 4;
     private static final int MAX_JOURNAL_SIZE = 1000000;
 
     // Journal key is such that it is always smaller than any dynamically generated
@@ -107,6 +109,7 @@
         Favorites.SPANY,                   // 15
         Favorites.TITLE,                   // 16
         Favorites.PROFILE_ID,              // 17
+        Favorites.RANK,                    // 18
     };
 
     private static final int ID_INDEX = 0;
@@ -126,6 +129,7 @@
     private static final int SPANX_INDEX = 14;
     private static final int SPANY_INDEX = 15;
     private static final int TITLE_INDEX = 16;
+    private static final int RANK_INDEX = 18;
 
     private static final String[] SCREEN_PROJECTION = {
         WorkspaceScreens._ID,              // 0
@@ -150,6 +154,9 @@
     private DeviceProfieData mDeviceProfileData;
     private InvariantDeviceProfile mIdp;
 
+    DeviceProfieData migrationCompatibleProfileData;
+    HashSet<String> widgetSizes = new HashSet<>();
+
     boolean restoreSuccessful;
     int restoredBackupVersion = 1;
 
@@ -286,9 +293,9 @@
             return true;
         }
 
-        boolean isHotsetCompatible = false;
+        boolean isHotseatCompatible = false;
         if (currentProfile.allappsRank >= oldProfile.hotseatCount) {
-            isHotsetCompatible = true;
+            isHotseatCompatible = true;
             mHotseatShift = 0;
         }
 
@@ -296,12 +303,29 @@
                 && ((currentProfile.hotseatCount - currentProfile.allappsRank) >=
                         (oldProfile.hotseatCount - oldProfile.allappsRank))) {
             // There is enough space on both sides of the hotseat.
-            isHotsetCompatible = true;
+            isHotseatCompatible = true;
             mHotseatShift = currentProfile.allappsRank - oldProfile.allappsRank;
         }
 
-        return isHotsetCompatible && (currentProfile.desktopCols >= oldProfile.desktopCols)
-                && (currentProfile.desktopRows >= oldProfile.desktopRows);
+        if (!isHotseatCompatible) {
+            return false;
+        }
+        if ((currentProfile.desktopCols >= oldProfile.desktopCols)
+                && (currentProfile.desktopRows >= oldProfile.desktopRows)) {
+            return true;
+        }
+
+        if (MigrateFromRestoreTask.ENABLED &&
+                (oldProfile.desktopCols - currentProfile.desktopCols <= 1) &&
+                (oldProfile.desktopRows - currentProfile.desktopRows <= 1)) {
+            // Allow desktop migration when row and/or column count contracts by 1.
+
+            migrationCompatibleProfileData = initDeviceProfileData(mIdp);
+            migrationCompatibleProfileData.desktopCols = oldProfile.desktopCols;
+            migrationCompatibleProfileData.desktopRows = oldProfile.desktopRows;
+            return true;
+        }
+        return false;
     }
 
     /**
@@ -441,7 +465,10 @@
                 Key key = getKey(Key.FAVORITE, id);
                 mKeys.add(key);
                 final String backupKey = keyToBackupKey(key);
-                if (!mExistingKeys.contains(backupKey) || updateTime >= mLastBackupRestoreTime) {
+
+                // Favorite proto changed in v4. Backup again if the version is old.
+                if (!mExistingKeys.contains(backupKey) || updateTime >= mLastBackupRestoreTime
+                        || restoredBackupVersion < 4) {
                     writeRowToBackup(key, packFavorite(cursor), data);
                 } else {
                     if (DEBUG) Log.d(TAG, "favorite already backup up: " + id);
@@ -648,7 +675,9 @@
                 } else {
                     Log.w(TAG, "empty intent on appwidget: " + id);
                 }
-                if (mExistingKeys.contains(backupKey) && restoredBackupVersion >= BACKUP_VERSION) {
+
+                // Widget backup proto changed in v3. So add it again if the original backup is old.
+                if (mExistingKeys.contains(backupKey) && restoredBackupVersion >= 3) {
                     if (DEBUG) Log.d(TAG, "already saved widget " + backupKey);
 
                     // remember that we already backed this up previously
@@ -699,7 +728,8 @@
             }
         }
 
-        // future site of widget table mutation
+        // Cache widget min sizes incase migration is required.
+        widgetSizes.add(widget.provider + "#" + widget.minSpanX + "," + widget.minSpanY);
     }
 
     /** create a new key, with an integer ID.
@@ -783,6 +813,7 @@
         favorite.spanX = c.getInt(SPANX_INDEX);
         favorite.spanY = c.getInt(SPANY_INDEX);
         favorite.iconType = c.getInt(ICON_TYPE_INDEX);
+        favorite.rank = c.getInt(RANK_INDEX);
 
         String title = c.getString(TITLE_INDEX);
         if (!TextUtils.isEmpty(title)) {
@@ -870,6 +901,7 @@
         values.put(Favorites.CELLY, favorite.cellY);
         values.put(Favorites.SPANX, favorite.spanX);
         values.put(Favorites.SPANY, favorite.spanY);
+        values.put(Favorites.RANK, favorite.rank);
 
         if (favorite.itemType == Favorites.ITEM_TYPE_SHORTCUT) {
             values.put(Favorites.ICON_TYPE, favorite.iconType);
@@ -895,7 +927,11 @@
                 UserManagerCompat.getInstance(mContext).getSerialNumberForUser(myUserHandle);
         values.put(LauncherSettings.Favorites.PROFILE_ID, userSerialNumber);
 
-        DeviceProfieData currentProfile = mDeviceProfileData;
+        // If we will attempt grid resize, use the original profile to validate grid size, as
+        // anything which fits in the original grid should fit in the current grid after
+        // grid migration.
+        DeviceProfieData currentProfile = migrationCompatibleProfileData == null
+                ? mDeviceProfileData : migrationCompatibleProfileData;
 
         if (favorite.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
             if (!TextUtils.isEmpty(favorite.appWidgetProvider)) {
@@ -987,14 +1023,9 @@
             widget.icon.dpi = dpi;
         }
 
-        // Calculate the spans corresponding to any one of the orientations as it should not change
-        // based on orientation.
-        int[] minSpans = CellLayout.rectToCell(
-                mIdp.portraitProfile, mContext, info.minResizeWidth, info.minResizeHeight, null);
-        widget.minSpanX = (info.resizeMode & LauncherAppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0
-                ? minSpans[0] : -1;
-        widget.minSpanY = (info.resizeMode & LauncherAppWidgetProviderInfo.RESIZE_VERTICAL) != 0
-                ? minSpans[1] : -1;
+        Point spans = info.getMinSpans(mIdp, mContext);
+        widget.minSpanX = spans.x;
+        widget.minSpanY = spans.y;
 
         return widget;
     }
@@ -1179,6 +1210,10 @@
         }
     }
 
+    public boolean shouldAttemptWorkspaceMigration() {
+        return migrationCompatibleProfileData != null;
+    }
+
     /**
      * A class to check if an activity can handle one of the intents from a list of
      * predefined intents.
diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java
index 6618cca..e34bd57 100644
--- a/src/com/android/launcher3/LauncherCallbacks.java
+++ b/src/com/android/launcher3/LauncherCallbacks.java
@@ -77,6 +77,7 @@
     public boolean providesSearch();
     public boolean startSearch(String initialQuery, boolean selectInitialQuery,
             Bundle appSearchData, Rect sourceBounds);
+    public boolean startSearchFromAllApps(String query);
     @Deprecated
     public void startVoice();
     public boolean hasCustomContentToLeft();
diff --git a/src/com/android/launcher3/LauncherClings.java b/src/com/android/launcher3/LauncherClings.java
index c13752c..458844c 100644
--- a/src/com/android/launcher3/LauncherClings.java
+++ b/src/com/android/launcher3/LauncherClings.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3;
 
-import android.accounts.Account;
-import android.accounts.AccountManager;
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
 import android.annotation.TargetApi;
@@ -29,23 +27,24 @@
 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;
 import android.view.accessibility.AccessibilityManager;
+
 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";
 
     private static final String TAG_CROP_TOP_AND_SIDES = "crop_bg_top_and_sides";
 
-    private static final boolean DISABLE_CLINGS = false;
-
     private static final int SHOW_CLING_DURATION = 250;
     private static final int DISMISS_CLING_DURATION = 200;
 
@@ -87,6 +86,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.
      *
@@ -94,6 +107,7 @@
      * package was not preinstalled and there exists a db to migrate from.
      */
     public void showMigrationCling() {
+        mLauncher.onLauncherClingShown();
         mLauncher.hideWorkspaceSearchAndHotseat();
 
         ViewGroup root = (ViewGroup) mLauncher.findViewById(R.id.launcher);
@@ -136,7 +150,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 +160,7 @@
             content.setBackground(bg);
         }
 
+        mLauncher.onLauncherClingShown();
         root.addView(cling);
 
         if (showWelcome) {
@@ -161,12 +178,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 +197,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);
             }
         };
@@ -215,10 +237,6 @@
     /** Returns whether the clings are enabled or should be shown */
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
     private boolean areClingsEnabled() {
-        if (DISABLE_CLINGS) {
-            return false;
-        }
-
         // disable clings when running in a test harness
         if(ActivityManager.isRunningInTestHarness()) return false;
 
@@ -231,10 +249,7 @@
 
         // Restricted secondary users (child mode) will potentially have very few apps
         // seeded when they start up for the first time. Clings won't work well with that
-        boolean supportsLimitedUsers =
-                android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
-        Account[] accounts = AccountManager.get(mLauncher).getAccounts();
-        if (supportsLimitedUsers && accounts.length == 0) {
+        if (Utilities.ATLEAST_JB_MR2) {
             UserManager um = (UserManager) mLauncher.getSystemService(Context.USER_SERVICE);
             Bundle restrictions = um.getUserRestrictions();
             if (restrictions.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, false)) {
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 1fbb06e..b390f13 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -17,6 +17,7 @@
 package com.android.launcher3;
 
 import android.app.SearchManager;
+import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -33,7 +34,6 @@
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.net.Uri;
-import android.os.Build;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -55,6 +55,8 @@
 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;
 import com.android.launcher3.util.CursorIconInfo;
@@ -203,7 +205,7 @@
         public void bindComponentsRemoved(ArrayList<String> packageNames,
                         ArrayList<AppInfo> appInfos, UserHandleCompat user, int reason);
         public void bindAllPackages(WidgetsModel model);
-        public void bindSearchablesChanged();
+        public void bindSearchProviderChanged();
         public boolean isAllAppsButtonRank(int rank);
         public void onPageBoundSynchronously(int page);
         public void dumpLogsToLocalData();
@@ -887,7 +889,7 @@
     }
 
     private void assertWorkspaceLoaded() {
-        if (LauncherAppState.isDogfoodBuild() && (isLoadingWorkspace() || !mHasLoaderCompletedOnce)) {
+        if (ProviderConfig.IS_DOGFOOD_BUILD && (isLoadingWorkspace() || !mHasLoaderCompletedOnce)) {
             throw new RuntimeException("Trying to add shortcut while loader is running");
         }
     }
@@ -1128,7 +1130,7 @@
      * Update the order of the workspace screens in the database. The array list contains
      * a list of screen ids in the order that they should appear.
      */
-    void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
+    public void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
         final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
         final ContentResolver cr = context.getContentResolver();
         final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
@@ -1275,14 +1277,14 @@
         if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
             // If we have changed locale we need to clear out the labels in all apps/workspace.
             forceReload();
-        } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action) ||
-                   SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED.equals(action)) {
+        } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action)) {
             Callbacks callbacks = getCallback();
             if (callbacks != null) {
-                callbacks.bindSearchablesChanged();
+                callbacks.bindSearchProviderChanged();
             }
         } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED.equals(action)
                 || LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
+            UserManagerCompat.getInstance(context).enableAndResetCache();
             forceReload();
         }
     }
@@ -1406,7 +1408,7 @@
     /**
      * Loads the workspace screen ids in an ordered list.
      */
-    @Thunk static ArrayList<Long> loadWorkspaceScreensDb(Context context) {
+    public static ArrayList<Long> loadWorkspaceScreensDb(Context context) {
         final ContentResolver contentResolver = context.getContentResolver();
         final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
 
@@ -1744,14 +1746,32 @@
             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;
 
+            if (MigrateFromRestoreTask.ENABLED && MigrateFromRestoreTask.shouldRunTask(mContext)) {
+                long migrationStartTime = System.currentTimeMillis();
+                Log.v(TAG, "Starting workspace migration after restore");
+                try {
+                    MigrateFromRestoreTask task = new MigrateFromRestoreTask(mContext);
+                    // Clear the flags before starting the task, so that we do not run the task
+                    // again, in case there was an uncaught error.
+                    MigrateFromRestoreTask.clearFlags(mContext);
+                    task.execute();
+                } catch (Exception e) {
+                    Log.e(TAG, "Error during grid migration", e);
+
+                    // Clear workspace.
+                    mFlags = mFlags | LOADER_FLAG_CLEAR_WORKSPACE;
+                }
+                Log.v(TAG, "Workspace migration completed in "
+                        + (System.currentTimeMillis() - migrationStartTime));
+            }
+
             if ((mFlags & LOADER_FLAG_CLEAR_WORKSPACE) != 0) {
                 Launcher.addDumpLog(TAG, "loadWorkspace: resetting launcher database", true);
                 LauncherAppState.getLauncherProvider().deleteDatabase();
@@ -2169,7 +2189,7 @@
 
                                             // Id would be valid only if the widget restore broadcast was received.
                                             if (isIdValid) {
-                                                status = LauncherAppWidgetInfo.RESTORE_COMPLETED;
+                                                status = LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
                                             } else {
                                                 status &= ~LauncherAppWidgetInfo
                                                         .FLAG_PROVIDER_NOT_READY;
@@ -2278,6 +2298,21 @@
                     }
                 }
 
+                // Sort all the folder items and make sure the first 3 items are high resolution.
+                for (FolderInfo folder : sBgFolders) {
+                    Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
+                    int pos = 0;
+                    for (ShortcutInfo info : folder.contents) {
+                        if (info.usingLowResIcon) {
+                            info.updateIcon(mIconCache, false);
+                        }
+                        pos ++;
+                        if (pos >= FolderIcon.NUM_ITEMS_IN_PREVIEW) {
+                            break;
+                        }
+                    }
+                }
+
                 if (restoredRows.size() > 0) {
                     // Update restored items that no longer require special handling
                     ContentValues values = new ContentValues();
@@ -2289,7 +2324,7 @@
 
                 if (!isSdCardReady && !sPendingPackages.isEmpty()) {
                     context.registerReceiver(new AppsAvailabilityCheck(),
-                            new IntentFilter(StartupReceiver.SYSTEM_READY),
+                            new IntentFilter(Intent.ACTION_BOOT_COMPLETED),
                             null, sWorker);
                 }
 
@@ -2461,7 +2496,7 @@
                                 return (int) (lhs.screenId - rhs.screenId);
                             }
                             default:
-                                if (LauncherAppState.isDogfoodBuild()) {
+                                if (ProviderConfig.IS_DOGFOOD_BUILD) {
                                     throw new RuntimeException("Unexpected container type when " +
                                             "sorting workspace items.");
                                 }
@@ -2841,8 +2876,7 @@
             // Cleanup any data stored for a deleted user.
             ManagedProfileHeuristic.processAllUsers(profiles, mContext);
 
-            loadAndBindWidgetsAndShortcuts(mApp.getContext(), tryGetCallbacks(oldCallbacks),
-                    true /* refresh */);
+            loadAndBindWidgetsAndShortcuts(tryGetCallbacks(oldCallbacks), true /* refresh */);
             if (DEBUG_LOADERS) {
                 Log.d(TAG, "Icons processed in "
                         + (SystemClock.uptimeMillis() - loadTime) + "ms");
@@ -2911,7 +2945,7 @@
         }
 
         // Reload widget list. No need to refresh, as we only want to update the icons and labels.
-        loadAndBindWidgetsAndShortcuts(mApp.getContext(), callbacks, false);
+        loadAndBindWidgetsAndShortcuts(callbacks, false);
     }
 
     void enqueuePackageUpdated(PackageUpdatedTask task) {
@@ -3174,6 +3208,12 @@
                                 widgetInfo.restoreStatus &=
                                         ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY &
                                         ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
+
+                                // adding this flag ensures that launcher shows 'click to setup'
+                                // if the widget has a config activity. In case there is no config
+                                // activity, it will be marked as 'restored' during bind.
+                                widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
+
                                 widgets.add(widgetInfo);
                                 updateItemInDatabase(context, widgetInfo);
                             }
@@ -3253,8 +3293,36 @@
                 });
             }
 
-            // onProvidersChanged method (API >= 17) already refreshed the widget list
-            loadAndBindWidgetsAndShortcuts(context, callbacks, Build.VERSION.SDK_INT < 17);
+            // Update widgets
+            if (mOp == OP_ADD || mOp == OP_REMOVE || mOp == OP_UPDATE) {
+                // Always refresh for a package event on secondary user
+                boolean needToRefresh = !mUser.equals(UserHandleCompat.myUserHandle());
+
+                // Refresh widget list, if the package already had a widget.
+                synchronized (sBgLock) {
+                    if (sBgWidgetProviders != null) {
+                        HashSet<String> pkgSet = new HashSet<>();
+                        Collections.addAll(pkgSet, mPackages);
+
+                        for (ComponentKey key : sBgWidgetProviders.keySet()) {
+                            needToRefresh |= key.user.equals(mUser) &&
+                                    pkgSet.contains(key.componentName.getPackageName());
+                        }
+                    }
+                }
+
+                if (!needToRefresh && mOp != OP_REMOVE) {
+                    // Refresh widget list, if there is any newly added widget
+                    PackageManager pm = context.getPackageManager();
+                    for (String pkg : mPackages) {
+                        needToRefresh |= !pm.queryBroadcastReceivers(
+                                new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE)
+                                    .setPackage(pkg), 0).isEmpty();
+                    }
+                }
+
+                loadAndBindWidgetsAndShortcuts(callbacks, needToRefresh);
+            }
 
             // Write all the logs to disk
             mHandler.post(new Runnable() {
@@ -3328,13 +3396,12 @@
         }
     }
 
-    public void loadAndBindWidgetsAndShortcuts(final Context context, final Callbacks callbacks,
-            final boolean refresh) {
+    public void loadAndBindWidgetsAndShortcuts(final Callbacks callbacks, final boolean refresh) {
 
         runOnWorkerThread(new Runnable() {
             @Override
             public void run() {
-                updateWidgetsModel(context, refresh);
+                updateWidgetsModel(refresh);
                 final WidgetsModel model = mBgWidgetsModel.clone();
 
                 mHandler.post(new Runnable() {
@@ -3358,10 +3425,10 @@
      *
      * @see #loadAndBindWidgetsAndShortcuts
      */
-    @Thunk void updateWidgetsModel(Context context, boolean refresh) {
-        PackageManager packageManager = context.getPackageManager();
+    @Thunk void updateWidgetsModel(boolean refresh) {
+        PackageManager packageManager = mApp.getContext().getPackageManager();
         final ArrayList<Object> widgetsAndShortcuts = new ArrayList<Object>();
-        widgetsAndShortcuts.addAll(getWidgetProviders(context, refresh));
+        widgetsAndShortcuts.addAll(getWidgetProviders(mApp.getContext(), refresh));
         Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
         widgetsAndShortcuts.addAll(packageManager.queryIntentActivities(shortcutsIntent, 0));
         mBgWidgetsModel.setWidgetsAndShortcuts(widgetsAndShortcuts);
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index cc5e18b..44a43e1 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -71,14 +71,12 @@
     private static final int DATABASE_VERSION = 26;
 
     static final String OLD_AUTHORITY = "com.android.launcher2.settings";
-    static final String AUTHORITY = ProviderConfig.AUTHORITY;
+    public static final String AUTHORITY = ProviderConfig.AUTHORITY;
 
     static final String TABLE_FAVORITES = LauncherSettings.Favorites.TABLE_NAME;
     static final String TABLE_WORKSPACE_SCREENS = LauncherSettings.WorkspaceScreens.TABLE_NAME;
     static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
 
-    private static final String URI_PARAM_IS_EXTERNAL_ADD = "isExternalAdd";
-
     private static final String RESTRICTION_PACKAGE_NAME = "workspace.configuration.package.name";
 
     @Thunk LauncherProviderChangeListener mListener;
@@ -140,14 +138,21 @@
         return db.insert(table, nullColumnHack, values);
     }
 
+    private void reloadLauncherIfExternal() {
+        if (Binder.getCallingPid() != Process.myPid()) {
+            LauncherAppState app = LauncherAppState.getInstanceNoCreate();
+            if (app != null) {
+                app.reloadWorkspace();
+            }
+        }
+    }
+
     @Override
     public Uri insert(Uri uri, ContentValues initialValues) {
         SqlArguments args = new SqlArguments(uri);
 
-        // In very limited cases, we support system|signature permission apps to add to the db
-        String externalAdd = uri.getQueryParameter(URI_PARAM_IS_EXTERNAL_ADD);
-        final boolean isExternalAll = externalAdd != null && "true".equals(externalAdd);
-        if (isExternalAll) {
+        // In very limited cases, we support system|signature permission apps to modify the db.
+        if (Binder.getCallingPid() != Process.myPid()) {
             if (!mOpenHelper.initializeExternalAdd(initialValues)) {
                 return null;
             }
@@ -161,13 +166,7 @@
         uri = ContentUris.withAppendedId(uri, rowId);
         notifyListeners();
 
-        if (isExternalAll) {
-            LauncherAppState app = LauncherAppState.getInstanceNoCreate();
-            if (app != null) {
-                app.reloadWorkspace();
-            }
-        }
-
+        reloadLauncherIfExternal();
         return uri;
     }
 
@@ -192,6 +191,7 @@
         }
 
         notifyListeners();
+        reloadLauncherIfExternal();
         return values.length;
     }
 
@@ -203,6 +203,7 @@
         try {
             ContentProviderResult[] result =  super.applyBatch(operations);
             db.setTransactionSuccessful();
+            reloadLauncherIfExternal();
             return result;
         } finally {
             db.endTransaction();
@@ -217,6 +218,7 @@
         int count = db.delete(args.table, args.where, args.args);
         if (count > 0) notifyListeners();
 
+        reloadLauncherIfExternal();
         return count;
     }
 
@@ -229,6 +231,7 @@
         int count = db.update(args.table, values, args.where, args.args);
         if (count > 0) notifyListeners();
 
+        reloadLauncherIfExternal();
         return count;
     }
 
@@ -399,7 +402,7 @@
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
     private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction() {
         // UserManager.getApplicationRestrictions() requires minSdkVersion >= 18
-        if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
+        if (!Utilities.ATLEAST_JB_MR2) {
             return null;
         }
 
@@ -1098,10 +1101,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);
 
@@ -1220,9 +1219,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/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index f2c85a1..8a5804f 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -143,7 +143,7 @@
          *
          * @return The unique content URL for the specified row.
          */
-        static Uri getContentUri(long id) {
+        public static Uri getContentUri(long id) {
             return Uri.parse("content://" + ProviderConfig.AUTHORITY +
                     "/" + TABLE_NAME + "/" + id);
         }
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
index 38ccfa7..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;
@@ -80,13 +82,6 @@
 public class LauncherStateTransitionAnimation {
 
     /**
-     * Callbacks made during the state transition
-     */
-    interface Callbacks {
-        public void onStateTransitionHideSearchBar();
-    }
-
-    /**
      * Private callbacks made during transition setup.
      */
     static abstract class PrivateTransitionCallbacks {
@@ -111,12 +106,10 @@
     public static final int SINGLE_FRAME_DELAY = 16;
 
     @Thunk Launcher mLauncher;
-    @Thunk Callbacks mCb;
-    @Thunk AnimatorSet mStateAnimation;
+    @Thunk AnimatorSet mCurrentAnimation;
 
-    public LauncherStateTransitionAnimation(Launcher l, Callbacks cb) {
+    public LauncherStateTransitionAnimation(Launcher l) {
         mLauncher = l;
-        mCb = cb;
     }
 
     /**
@@ -125,8 +118,8 @@
      * @param startSearchAfterTransition Immediately starts app search after the transition to
      *                                   All Apps is completed.
      */
-    public void startAnimationToAllApps(final boolean animated,
-            final boolean startSearchAfterTransition) {
+    public void startAnimationToAllApps(final Workspace.State fromWorkspaceState,
+            final boolean animated, final boolean startSearchAfterTransition) {
         final AllAppsContainerView toView = mLauncher.getAppsView();
         final View buttonView = mLauncher.getAllAppsButton();
         PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
@@ -159,15 +152,16 @@
             }
         };
         // Only animate the search bar if animating from spring loaded mode back to all apps
-        startAnimationToOverlay(Workspace.State.NORMAL_HIDDEN, buttonView, toView,
-                toView.getContentView(), toView.getRevealView(), toView.getSearchBarView(),
-                animated, true /* hideSearchBar */, cb);
+        mCurrentAnimation = startAnimationToOverlay(fromWorkspaceState,
+                Workspace.State.NORMAL_HIDDEN, buttonView, toView, toView.getContentView(),
+                toView.getRevealView(), toView.getSearchBarView(), animated, cb);
     }
 
     /**
      * Starts an animation to the widgets view.
      */
-    public void startAnimationToWidgets(final boolean animated) {
+    public void startAnimationToWidgets(final Workspace.State fromWorkspaceState,
+            final boolean animated) {
         final WidgetsContainerView toView = mLauncher.getWidgetsView();
         final View buttonView = mLauncher.getWidgetsButton();
         PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
@@ -176,17 +170,17 @@
                 return 0.3f;
             }
         };
-        startAnimationToOverlay(Workspace.State.OVERVIEW_HIDDEN, buttonView, toView,
-                toView.getContentView(), toView.getRevealView(), null, animated,
-                true /* hideSearchBar */, cb);
+        mCurrentAnimation = startAnimationToOverlay(fromWorkspaceState,
+                Workspace.State.OVERVIEW_HIDDEN, buttonView, toView, toView.getContentView(),
+                toView.getRevealView(), null, animated, cb);
     }
 
     /**
      * Starts and animation to the workspace from the current overlay view.
      */
     public void startAnimationToWorkspace(final Launcher.State fromState,
-              final Workspace.State toWorkspaceState,
-              final boolean animated, final Runnable onCompleteRunnable) {
+            final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
+            final boolean animated, final Runnable onCompleteRunnable) {
         if (toWorkspaceState != Workspace.State.NORMAL &&
                 toWorkspaceState != Workspace.State.SPRING_LOADED &&
                 toWorkspaceState != Workspace.State.OVERVIEW) {
@@ -194,9 +188,11 @@
         }
 
         if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED) {
-            startAnimationToWorkspaceFromAllApps(toWorkspaceState, animated, onCompleteRunnable);
+            startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState,
+                    animated, onCompleteRunnable);
         } else {
-            startAnimationToWorkspaceFromWidgets(toWorkspaceState, animated, onCompleteRunnable);
+            startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState,
+                    animated, onCompleteRunnable);
         }
     }
 
@@ -204,12 +200,13 @@
      * Creates and starts a new animation to a particular overlay view.
      */
     @SuppressLint("NewApi")
-    private void startAnimationToOverlay(final Workspace.State toWorkspaceState,
-            final View buttonView, final View toView, final View contentView, final View revealView,
-            final View overlaySearchBarView, final boolean animated, final boolean hideSearchBar,
-            final PrivateTransitionCallbacks pCb) {
+    private AnimatorSet startAnimationToOverlay(final Workspace.State fromWorkspaceState,
+            final Workspace.State toWorkspaceState, final View buttonView, final View toView,
+            final View contentView, final View revealView, final View overlaySearchBarView,
+            final boolean animated, final PrivateTransitionCallbacks pCb) {
+        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
         final Resources res = mLauncher.getResources();
-        final boolean material = Utilities.isLmpOrAbove();
+        final boolean material = Utilities.ATLEAST_LOLLIPOP;
         final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
         final int itemsAlphaStagger =
                 res.getInteger(R.integer.config_overlayItemsAlphaStagger);
@@ -227,11 +224,13 @@
         // Create the workspace animation.
         // NOTE: this call apparently also sets the state for the workspace if !animated
         Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState,
-                animated, overlaySearchBarView != null /* hasOverlaySearchBar */, layerViews);
+                animated, layerViews);
+
+        // Animate the search bar
+        startWorkspaceSearchBarAnimation(animation, fromWorkspaceState, toWorkspaceState,
+                animated ? revealDuration : 0, overlaySearchBarView);
 
         if (animated && initialized) {
-            mStateAnimation = LauncherAnimUtils.createAnimatorSet();
-
             // Setup the reveal view animation
             int width = revealView.getMeasuredWidth();
             int height = revealView.getMeasuredHeight();
@@ -259,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);
@@ -271,7 +270,7 @@
 
             // Play the animation
             layerViews.put(revealView, BUILD_AND_SET_LAYER);
-            mStateAnimation.play(panelAlphaAndDrift);
+            animation.play(panelAlphaAndDrift);
 
             if (overlaySearchBarView != null) {
                 overlaySearchBarView.setAlpha(0f);
@@ -279,7 +278,7 @@
                 searchBarAlpha.setDuration(100);
                 searchBarAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
                 layerViews.put(overlaySearchBarView, BUILD_AND_SET_LAYER);
-                mStateAnimation.play(searchBarAlpha);
+                animation.play(searchBarAlpha);
             }
 
             // Setup the animation for the content view
@@ -294,13 +293,13 @@
             pageDrift.setDuration(revealDuration);
             pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
             pageDrift.setStartDelay(itemsAlphaStagger);
-            mStateAnimation.play(pageDrift);
+            animation.play(pageDrift);
 
             ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 0f, 1f);
             itemsAlpha.setDuration(revealDuration);
             itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
             itemsAlpha.setStartDelay(itemsAlphaStagger);
-            mStateAnimation.play(itemsAlpha);
+            animation.play(itemsAlpha);
 
             if (material) {
                 float startRadius = pCb.getMaterialRevealViewStartFinalRadius();
@@ -313,10 +312,10 @@
                 if (listener != null) {
                     reveal.addListener(listener);
                 }
-                mStateAnimation.play(reveal);
+                animation.play(reveal);
             }
 
-            mStateAnimation.addListener(new AnimatorListenerAdapter() {
+            animation.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
                     dispatchOnLauncherTransitionEnd(fromView, animated, false);
@@ -332,12 +331,8 @@
                         }
                     }
 
-                    if (hideSearchBar) {
-                        mCb.onStateTransitionHideSearchBar();
-                    }
-
                     // This can hold unnecessary references to views.
-                    mStateAnimation = null;
+                    cleanupAnimation();
                     pCb.onTransitionComplete();
                 }
 
@@ -345,7 +340,7 @@
 
             // Play the workspace animation
             if (workspaceAnim != null) {
-                mStateAnimation.play(workspaceAnim);
+                animation.play(workspaceAnim);
             }
 
             // Dispatch the prepare transition signal
@@ -353,23 +348,22 @@
             dispatchOnLauncherTransitionPrepare(toView, animated, false);
 
 
-            final AnimatorSet stateAnimation = mStateAnimation;
+            final AnimatorSet stateAnimation = animation;
             final Runnable startAnimRunnable = new Runnable() {
                 public void run() {
-                    // Check that mStateAnimation hasn't changed while
+                    // Check that mCurrentAnimation hasn't changed while
                     // we waited for a layout/draw pass
-                    if (mStateAnimation != stateAnimation)
+                    if (mCurrentAnimation != stateAnimation)
                         return;
                     dispatchOnLauncherTransitionStart(fromView, animated, false);
                     dispatchOnLauncherTransitionStart(toView, animated, false);
 
                     // Enable all necessary layers
-                    boolean isLmpOrAbove = Utilities.isLmpOrAbove();
                     for (View v : layerViews.keySet()) {
                         if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
                             v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
                         }
-                        if (isLmpOrAbove && Utilities.isViewAttachedToWindow(v)) {
+                        if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
                             v.buildLayer();
                         }
                     }
@@ -377,12 +371,14 @@
                     // Focus the new view
                     toView.requestFocus();
 
-                    mStateAnimation.start();
+                    stateAnimation.start();
                 }
             };
             toView.bringToFront();
             toView.setVisibility(View.VISIBLE);
             toView.post(startAnimRunnable);
+
+            return animation;
         } else {
             toView.setTranslationX(0.0f);
             toView.setTranslationY(0.0f);
@@ -394,10 +390,6 @@
             // Show the content view
             contentView.setVisibility(View.VISIBLE);
 
-            if (hideSearchBar) {
-                mCb.onStateTransitionHideSearchBar();
-            }
-
             dispatchOnLauncherTransitionPrepare(fromView, animated, false);
             dispatchOnLauncherTransitionStart(fromView, animated, false);
             dispatchOnLauncherTransitionEnd(fromView, animated, false);
@@ -405,18 +397,19 @@
             dispatchOnLauncherTransitionStart(toView, animated, false);
             dispatchOnLauncherTransitionEnd(toView, animated, false);
             pCb.onTransitionComplete();
+
+            return null;
         }
     }
 
     /**
      * Starts and animation to the workspace from the apps view.
      */
-    private void startAnimationToWorkspaceFromAllApps(final Workspace.State toWorkspaceState,
-            final boolean animated, final Runnable onCompleteRunnable) {
+    private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState,
+            final Workspace.State toWorkspaceState, final boolean animated, 
+            final Runnable onCompleteRunnable) {
         AllAppsContainerView appsView = mLauncher.getAppsView();
         PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
-            int[] mAllAppsToPanelDelta;
-
             @Override
             float getMaterialRevealViewFinalAlpha(View revealView) {
                 // No alpha anim from all apps
@@ -448,16 +441,18 @@
             }
         };
         // Only animate the search bar if animating to spring loaded mode from all apps
-        startAnimationToWorkspaceFromOverlay(toWorkspaceState, mLauncher.getAllAppsButton(),
-                appsView, appsView.getContentView(), appsView.getRevealView(),
-                appsView.getSearchBarView(), animated, onCompleteRunnable, cb);
+        mCurrentAnimation = startAnimationToWorkspaceFromOverlay(fromWorkspaceState, toWorkspaceState,
+                mLauncher.getAllAppsButton(), appsView, appsView.getContentView(),
+                appsView.getRevealView(), appsView.getSearchBarView(), animated,
+                onCompleteRunnable, cb);
     }
 
     /**
      * Starts and animation to the workspace from the widgets view.
      */
-    private void startAnimationToWorkspaceFromWidgets(final Workspace.State toWorkspaceState,
-              final boolean animated, final Runnable onCompleteRunnable) {
+    private void startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState,
+            final Workspace.State toWorkspaceState, final boolean animated, 
+            final Runnable onCompleteRunnable) {
         final WidgetsContainerView widgetsView = mLauncher.getWidgetsView();
         PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
             @Override
@@ -475,20 +470,23 @@
                 };
             }
         };
-        startAnimationToWorkspaceFromOverlay(toWorkspaceState, mLauncher.getWidgetsButton(),
-                widgetsView, widgetsView.getContentView(), widgetsView.getRevealView(), null,
-                animated, onCompleteRunnable, cb);
-        }
+        mCurrentAnimation = startAnimationToWorkspaceFromOverlay(fromWorkspaceState,
+                toWorkspaceState, mLauncher.getWidgetsButton(), widgetsView,
+                widgetsView.getContentView(), widgetsView.getRevealView(), null, animated,
+                onCompleteRunnable, cb);
+    }
 
     /**
      * Creates and starts a new animation to the workspace.
      */
-    private void startAnimationToWorkspaceFromOverlay(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) {
+    private AnimatorSet startAnimationToWorkspaceFromOverlay(final Workspace.State fromWorkspaceState,
+            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) {
+        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
         final Resources res = mLauncher.getResources();
-        final boolean material = Utilities.isLmpOrAbove();
+        final boolean material = Utilities.ATLEAST_LOLLIPOP;
         final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
         final int itemsAlphaStagger =
                 res.getInteger(R.integer.config_overlayItemsAlphaStagger);
@@ -506,15 +504,16 @@
         // Create the workspace animation.
         // NOTE: this call apparently also sets the state for the workspace if !animated
         Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState,
-                animated, overlaySearchBarView != null /* hasOverlaySearchBar */,
-                layerViews);
+                animated, layerViews);
+
+        // Animate the search bar
+        startWorkspaceSearchBarAnimation(animation, fromWorkspaceState, toWorkspaceState,
+                animated ? revealDuration : 0, overlaySearchBarView);
 
         if (animated && initialized) {
-            mStateAnimation = LauncherAnimUtils.createAnimatorSet();
-
             // Play the workspace animation
             if (workspaceAnim != null) {
-                mStateAnimation.play(workspaceAnim);
+                animation.play(workspaceAnim);
             }
 
             // hideAppsCustomizeHelper is called in some cases when it is already hidden
@@ -553,14 +552,14 @@
                 panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY);
                 panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
                 panelDriftY.setInterpolator(decelerateInterpolator);
-                mStateAnimation.play(panelDriftY);
+                animation.play(panelDriftY);
 
                 ObjectAnimator panelDriftX = ObjectAnimator.ofFloat(revealView, "translationX",
                         0, revealViewToXDrift);
                 panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY);
                 panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
                 panelDriftX.setInterpolator(decelerateInterpolator);
-                mStateAnimation.play(panelDriftX);
+                animation.play(panelDriftX);
 
                 // Setup animation for the reveal panel alpha
                 final float revealViewToAlpha = !material ? 0f :
@@ -571,7 +570,7 @@
                     panelAlpha.setDuration(material ? revealDuration : 150);
                     panelAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY);
                     panelAlpha.setInterpolator(decelerateInterpolator);
-                    mStateAnimation.play(panelAlpha);
+                    animation.play(panelAlpha);
                 }
 
                 // Setup the animation for the content view
@@ -584,13 +583,13 @@
                 pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY);
                 pageDrift.setInterpolator(decelerateInterpolator);
                 pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
-                mStateAnimation.play(pageDrift);
+                animation.play(pageDrift);
 
                 contentView.setAlpha(1f);
                 ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 1f, 0f);
                 itemsAlpha.setDuration(100);
                 itemsAlpha.setInterpolator(decelerateInterpolator);
-                mStateAnimation.play(itemsAlpha);
+                animation.play(itemsAlpha);
 
                 if (overlaySearchBarView != null) {
                     overlaySearchBarView.setAlpha(1f);
@@ -599,7 +598,7 @@
                     searchAlpha.setInterpolator(decelerateInterpolator);
                     searchAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY);
                     layerViews.put(overlaySearchBarView, BUILD_AND_SET_LAYER);
-                    mStateAnimation.play(searchAlpha);
+                    animation.play(searchAlpha);
                 }
 
                 if (material) {
@@ -615,14 +614,14 @@
                     if (listener != null) {
                         reveal.addListener(listener);
                     }
-                    mStateAnimation.play(reveal);
+                    animation.play(reveal);
                 }
 
                 dispatchOnLauncherTransitionPrepare(fromView, animated, true);
                 dispatchOnLauncherTransitionPrepare(toView, animated, true);
             }
 
-            mStateAnimation.addListener(new AnimatorListenerAdapter() {
+            animation.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
                     fromView.setVisibility(View.GONE);
@@ -652,35 +651,38 @@
                     }
 
                     // This can hold unnecessary references to views.
-                    mStateAnimation = null;
+                    cleanupAnimation();
                     pCb.onTransitionComplete();
                 }
             });
 
-            final AnimatorSet stateAnimation = mStateAnimation;
+            final AnimatorSet stateAnimation = animation;
             final Runnable startAnimRunnable = new Runnable() {
+                @TargetApi(Build.VERSION_CODES.LOLLIPOP)
                 public void run() {
-                    // Check that mStateAnimation hasn't changed while
+                    // Check that mCurrentAnimation hasn't changed while
                     // we waited for a layout/draw pass
-                    if (mStateAnimation != stateAnimation)
+                    if (mCurrentAnimation != stateAnimation)
                         return;
+
                     dispatchOnLauncherTransitionStart(fromView, animated, false);
                     dispatchOnLauncherTransitionStart(toView, animated, false);
 
                     // Enable all necessary layers
-                    boolean isLmpOrAbove = Utilities.isLmpOrAbove();
                     for (View v : layerViews.keySet()) {
                         if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
                             v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
                         }
-                        if (isLmpOrAbove && Utilities.isViewAttachedToWindow(v)) {
+                        if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
                             v.buildLayer();
                         }
                     }
-                    mStateAnimation.start();
+                    stateAnimation.start();
                 }
             };
             fromView.post(startAnimRunnable);
+
+            return animation;
         } else {
             fromView.setVisibility(View.GONE);
             dispatchOnLauncherTransitionPrepare(fromView, animated, true);
@@ -695,9 +697,44 @@
             if (onCompleteRunnable != null) {
                 onCompleteRunnable.run();
             }
+
+            return null;
         }
     }
 
+    /**
+     * Coordinates the workspace search bar animation along with the launcher state animation.
+     */
+    private void startWorkspaceSearchBarAnimation(AnimatorSet animation,
+            final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, int duration,
+            View overlaySearchBar) {
+        final SearchDropTargetBar.State toSearchBarState =
+                toWorkspaceState.getSearchDropTargetBarState();
+
+        if (overlaySearchBar != null) {
+            if ((toWorkspaceState == Workspace.State.NORMAL) &&
+                    (fromWorkspaceState == Workspace.State.NORMAL_HIDDEN)) {
+                // If we are transitioning from the overlay to the workspace, then show the
+                // workspace search bar immediately and let the overlay search bar fade out on top
+                mLauncher.getSearchDropTargetBar().animateToState(toSearchBarState, 0);
+            } else if (fromWorkspaceState == Workspace.State.NORMAL) {
+                // If we are transitioning from the workspace to the overlay, then keep the
+                // workspace search bar visible until the overlay search bar fades in on top
+                animation.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        mLauncher.getSearchDropTargetBar().animateToState(toSearchBarState, 0);
+                    }
+                });
+            } else {
+                // Otherwise, then just animate the workspace search bar normally
+                mLauncher.getSearchDropTargetBar().animateToState(toSearchBarState, duration);
+            }
+        } else {
+            // If there is no overlay search bar, then just animate the workspace search bar
+            mLauncher.getSearchDropTargetBar().animateToState(toSearchBarState, duration);
+        }
+    }
 
     /**
      * Dispatches the prepare-transition event to suitable views.
@@ -748,10 +785,14 @@
      * Cancels the current animation.
      */
     private void cancelAnimation() {
-        if (mStateAnimation != null) {
-            mStateAnimation.setDuration(0);
-            mStateAnimation.cancel();
-            mStateAnimation = null;
+        if (mCurrentAnimation != null) {
+            mCurrentAnimation.setDuration(0);
+            mCurrentAnimation.cancel();
+            mCurrentAnimation = null;
         }
     }
+
+    @Thunk void cleanupAnimation() {
+        mCurrentAnimation = null;
+    }
 }
diff --git a/src/com/android/launcher3/LauncherViewPropertyAnimator.java b/src/com/android/launcher3/LauncherViewPropertyAnimator.java
index 4cafbbf..4406a2c 100644
--- a/src/com/android/launcher3/LauncherViewPropertyAnimator.java
+++ b/src/com/android/launcher3/LauncherViewPropertyAnimator.java
@@ -26,6 +26,7 @@
 import java.util.EnumSet;
 
 public class LauncherViewPropertyAnimator extends Animator implements AnimatorListener {
+
     enum Properties {
             TRANSLATION_X,
             TRANSLATION_Y,
@@ -51,13 +52,12 @@
     long mStartDelay;
     long mDuration;
     TimeInterpolator mInterpolator;
-    ArrayList<Animator.AnimatorListener> mListeners;
+    ArrayList<Animator.AnimatorListener> mListeners = new ArrayList<>();
     boolean mRunning = false;
     FirstFrameAnimatorHelper mFirstFrameHelper;
 
     public LauncherViewPropertyAnimator(View target) {
         mTarget = target;
-        mListeners = new ArrayList<Animator.AnimatorListener>();
     }
 
     @Override
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index c9bbe0a..28e3367 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -18,7 +18,6 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
 import android.animation.LayoutTransition;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
@@ -64,7 +63,7 @@
     // the min drag distance for a fling to register, to prevent random page shifts
     private static final int MIN_LENGTH_FOR_FLING = 25;
 
-    protected static final int PAGE_SNAP_ANIMATION_DURATION = 750;
+    public static final int PAGE_SNAP_ANIMATION_DURATION = 750;
     protected static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950;
 
     private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f;
@@ -180,7 +179,7 @@
     private Runnable mPostReorderingPreZoomInRunnable;
 
     // Convenience/caching
-    protected static final Matrix sTmpInvMatrix = new Matrix();
+    private static final Matrix sTmpInvMatrix = new Matrix();
     private static final float[] sTmpPoint = new float[2];
     private static final int[] sTmpIntPoint = new int[2];
     private static final Rect sTmpRect = new Rect();
@@ -382,7 +381,7 @@
     /**
      * Returns the index of page to be shown immediately afterwards.
      */
-    int getNextPage() {
+    public int getNextPage() {
         return (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
     }
 
@@ -1681,16 +1680,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);
                                 }
@@ -2140,13 +2137,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) {
@@ -2243,8 +2239,6 @@
         animateDragViewToOriginalPosition();
     }
 
-    private static final int ANIM_TAG_KEY = 100;
-
     /* Accessibility */
     @SuppressWarnings("deprecation")
     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
@@ -2264,7 +2258,7 @@
         // Besides disabling the accessibility long-click, this also prevents this view from getting
         // accessibility focus.
         info.setLongClickable(false);
-        if (Utilities.isLmpOrAbove()) {
+        if (Utilities.ATLEAST_LOLLIPOP) {
             info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
         }
     }
diff --git a/src/com/android/launcher3/PendingAppWidgetHostView.java b/src/com/android/launcher3/PendingAppWidgetHostView.java
index 08f8e56..40eadab 100644
--- a/src/com/android/launcher3/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/PendingAppWidgetHostView.java
@@ -16,13 +16,17 @@
 
 package com.android.launcher3;
 
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources.Theme;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
 import android.os.Bundle;
 import android.text.Layout;
 import android.text.StaticLayout;
@@ -32,6 +36,8 @@
 import android.view.View.OnClickListener;
 
 public class PendingAppWidgetHostView extends LauncherAppWidgetHostView implements OnClickListener {
+    private static final float SETUP_ICON_SIZE_FACTOR = 2f / 5;
+    private static final float MIN_SATUNATION = 0.7f;
 
     private static Theme sPreloaderTheme;
 
@@ -47,13 +53,14 @@
     private Bitmap mIcon;
 
     private Drawable mCenterDrawable;
-    private Drawable mTopCornerDrawable;
+    private Drawable mSettingIconDrawable;
 
     private boolean mDrawableSizeChanged;
 
     private final TextPaint mPaint;
     private Layout mSetupTextLayout;
 
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
     public PendingAppWidgetHostView(Context context, LauncherAppWidgetInfo info,
             boolean disabledForSafeMode) {
         super(context);
@@ -70,6 +77,10 @@
                 mLauncher.getDeviceProfile().iconTextSizePx, getResources().getDisplayMetrics()));
         setBackgroundResource(R.drawable.quantum_panel_dark);
         setWillNotDraw(false);
+
+        if (Utilities.ATLEAST_LOLLIPOP) {
+            setElevation(getResources().getDimension(R.dimen.pending_widget_elevation));
+        }
     }
 
     @Override
@@ -124,10 +135,12 @@
                 FastBitmapDrawable disabledIcon = mLauncher.createIconDrawable(mIcon);
                 disabledIcon.setGhostModeEnabled(true);
                 mCenterDrawable = disabledIcon;
-                mTopCornerDrawable = null;
+                mSettingIconDrawable = null;
             } else if (isReadyForClickSetup()) {
-                mCenterDrawable = getResources().getDrawable(R.drawable.ic_setting);
-                mTopCornerDrawable = new FastBitmapDrawable(mIcon);
+                mCenterDrawable = new FastBitmapDrawable(mIcon);
+                mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate();
+
+                updateSettingColor();
             } else {
                 if (sPreloaderTheme == null) {
                     sPreloaderTheme = getResources().newTheme();
@@ -137,13 +150,25 @@
                 FastBitmapDrawable drawable = mLauncher.createIconDrawable(mIcon);
                 mCenterDrawable = new PreloadIconDrawable(drawable, sPreloaderTheme);
                 mCenterDrawable.setCallback(this);
-                mTopCornerDrawable = null;
+                mSettingIconDrawable = null;
                 applyState();
             }
             mDrawableSizeChanged = true;
         }
     }
 
+    private void updateSettingColor() {
+        int color = Utilities.findDominantColorByHue(mIcon, 20);
+        // Make the dominant color bright.
+        float[] hsv = new float[3];
+        Color.colorToHSV(color, hsv);
+        hsv[1] = Math.min(hsv[1], MIN_SATUNATION);
+        hsv[2] = 1;
+        color = Color.HSVToColor(hsv);
+
+        mSettingIconDrawable.setColorFilter(color,  PorterDuff.Mode.SRC_IN);
+    }
+
     @Override
     protected boolean verifyDrawable(Drawable who) {
         return (who == mCenterDrawable) || super.verifyDrawable(who);
@@ -169,6 +194,83 @@
                 && (mInfo.restoreStatus & LauncherAppWidgetInfo.FLAG_UI_NOT_READY) != 0;
     }
 
+    private void updateDrawableBounds() {
+        DeviceProfile grid = mLauncher.getDeviceProfile();
+        int paddingTop = getPaddingTop();
+        int paddingBottom = getPaddingBottom();
+        int paddingLeft = getPaddingLeft();
+        int paddingRight = getPaddingRight();
+
+        int minPadding = getResources()
+                .getDimensionPixelSize(R.dimen.pending_widget_min_padding);
+
+        int availableWidth = getWidth() - paddingLeft - paddingRight - 2 * minPadding;
+        int availableHeight = getHeight() - paddingTop - paddingBottom - 2 * minPadding;
+
+        if (mSettingIconDrawable == null) {
+            int outset = (mCenterDrawable instanceof PreloadIconDrawable) ?
+                    ((PreloadIconDrawable) mCenterDrawable).getOutset() : 0;
+            int maxSize = grid.iconSizePx + 2 * outset;
+            int size = Math.min(maxSize, Math.min(availableWidth, availableHeight));
+
+            mRect.set(0, 0, size, size);
+            mRect.inset(outset, outset);
+            mRect.offsetTo((getWidth() - mRect.width()) / 2, (getHeight() - mRect.height()) / 2);
+            mCenterDrawable.setBounds(mRect);
+        } else  {
+            float iconSize = Math.min(availableWidth, availableHeight);
+
+            // Use twice the setting size factor, as the setting is drawn at a corner and the
+            // icon is drawn in the center.
+            float settingIconScaleFactor = 1 + SETUP_ICON_SIZE_FACTOR * 2;
+            int maxSize = Math.max(availableWidth, availableHeight);
+            if (iconSize * settingIconScaleFactor > maxSize) {
+                // There is an overlap
+                iconSize = maxSize / settingIconScaleFactor;
+            }
+
+            int actualIconSize = (int) Math.min(iconSize, grid.iconSizePx);
+
+            // Recreate the setup text.
+            mSetupTextLayout = new StaticLayout(
+                    getResources().getText(R.string.gadget_setup_text), mPaint, availableWidth,
+                    Layout.Alignment.ALIGN_CENTER, 1, 0, true);
+            int textHeight = mSetupTextLayout.getHeight();
+
+            // Extra icon size due to the setting icon
+            float minHeightWithText = textHeight + actualIconSize * settingIconScaleFactor
+                    + grid.iconDrawablePaddingPx;
+
+            int iconTop;
+            if (minHeightWithText < availableHeight) {
+                // We can draw the text as well
+                iconTop =  (getHeight() - textHeight -
+                        grid.iconDrawablePaddingPx - actualIconSize) / 2;
+
+            } else {
+                // The text will not fit. Only draw the icons.
+                iconTop = (getHeight() - actualIconSize) / 2;
+                mSetupTextLayout = null;
+            }
+
+            mRect.set(0, 0, actualIconSize, actualIconSize);
+            mRect.offset((getWidth() - actualIconSize) / 2, iconTop);
+            mCenterDrawable.setBounds(mRect);
+
+            mRect.left = paddingLeft + minPadding;
+            mRect.right = mRect.left + (int) (SETUP_ICON_SIZE_FACTOR * actualIconSize);
+            mRect.top = paddingTop + minPadding;
+            mRect.bottom = mRect.top + (int) (SETUP_ICON_SIZE_FACTOR * actualIconSize);
+            mSettingIconDrawable.setBounds(mRect);
+
+            if (mSetupTextLayout != null) {
+                // Set up position for dragging the text
+                mRect.left = paddingLeft + minPadding;
+                mRect.top = mCenterDrawable.getBounds().bottom + grid.iconDrawablePaddingPx;
+            }
+        }
+    }
+
     @Override
     protected void onDraw(Canvas canvas) {
         if (mCenterDrawable == null) {
@@ -176,81 +278,21 @@
             return;
         }
 
-        DeviceProfile grid = mLauncher.getDeviceProfile();
-        if (mTopCornerDrawable == null) {
-            if (mDrawableSizeChanged) {
-                int outset = (mCenterDrawable instanceof PreloadIconDrawable) ?
-                        ((PreloadIconDrawable) mCenterDrawable).getOutset() : 0;
-                int maxSize = grid.iconSizePx + 2 * outset;
-                int size = Math.min(maxSize, Math.min(
-                        getWidth() - getPaddingLeft() - getPaddingRight(),
-                        getHeight() - getPaddingTop() - getPaddingBottom()));
-
-                mRect.set(0, 0, size, size);
-                mRect.inset(outset, outset);
-                mRect.offsetTo((getWidth() - mRect.width()) / 2, (getHeight() - mRect.height()) / 2);
-                mCenterDrawable.setBounds(mRect);
-                mDrawableSizeChanged = false;
-            }
-            mCenterDrawable.draw(canvas);
-        } else  {
-            // Draw the top corner icon and "Setup" text is possible
-            if (mDrawableSizeChanged) {
-                int iconSize = grid.iconSizePx;
-                int paddingTop = getPaddingTop();
-                int paddingBottom = getPaddingBottom();
-                int paddingLeft = getPaddingLeft();
-                int paddingRight = getPaddingRight();
-
-                int availableWidth = getWidth() - paddingLeft - paddingRight;
-                int availableHeight = getHeight() - paddingTop - paddingBottom;
-
-                // Recreate the setup text.
-                mSetupTextLayout = new StaticLayout(
-                        getResources().getText(R.string.gadget_setup_text), mPaint, availableWidth,
-                        Layout.Alignment.ALIGN_CENTER, 1, 0, true);
-                if (mSetupTextLayout.getLineCount() == 1) {
-                    // The text fits in a single line. No need to draw the setup icon.
-                    int size = Math.min(iconSize, Math.min(availableWidth,
-                            availableHeight - mSetupTextLayout.getHeight()));
-                    mRect.set(0, 0, size, size);
-                    mRect.offsetTo((getWidth() - mRect.width()) / 2,
-                            (getHeight() - mRect.height() - mSetupTextLayout.getHeight()
-                                    - grid.iconDrawablePaddingPx) / 2);
-
-                    mTopCornerDrawable.setBounds(mRect);
-
-                    // Update left and top to indicate the position where the text will be drawn.
-                    mRect.left = paddingLeft;
-                    mRect.top = mRect.bottom + grid.iconDrawablePaddingPx;
-                } else {
-                    // The text can't be drawn in a single line. Draw a setup icon instead.
-                    mSetupTextLayout = null;
-                    int size = Math.min(iconSize, Math.min(
-                            getWidth() - paddingLeft - paddingRight,
-                            getHeight() - paddingTop - paddingBottom));
-                    mRect.set(0, 0, size, size);
-                    mRect.offsetTo((getWidth() - mRect.width()) / 2, (getHeight() - mRect.height()) / 2);
-                    mCenterDrawable.setBounds(mRect);
-
-                    size = Math.min(size / 2,
-                            Math.max(mRect.top - paddingTop, mRect.left - paddingLeft));
-                    mTopCornerDrawable.setBounds(paddingLeft, paddingTop,
-                            paddingLeft + size, paddingTop + size);
-                }
-                mDrawableSizeChanged = false;
-            }
-
-            if (mSetupTextLayout == null) {
-                mCenterDrawable.draw(canvas);
-                mTopCornerDrawable.draw(canvas);
-            } else {
-                canvas.save();
-                canvas.translate(mRect.left, mRect.top);
-                mSetupTextLayout.draw(canvas);
-                canvas.restore();
-                mTopCornerDrawable.draw(canvas);
-            }
+        if (mDrawableSizeChanged) {
+            updateDrawableBounds();
+            mDrawableSizeChanged = false;
         }
+
+        mCenterDrawable.draw(canvas);
+        if (mSettingIconDrawable != null) {
+            mSettingIconDrawable.draw(canvas);
+        }
+        if (mSetupTextLayout != null) {
+            canvas.save();
+            canvas.translate(mRect.left, mRect.top);
+            mSetupTextLayout.draw(canvas);
+            canvas.restore();
+        }
+
     }
 }
diff --git a/src/com/android/launcher3/PreloadIconDrawable.java b/src/com/android/launcher3/PreloadIconDrawable.java
index bcb59c4..45e4b2c 100644
--- a/src/com/android/launcher3/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/PreloadIconDrawable.java
@@ -213,6 +213,10 @@
         return mAnimationProgress;
     }
 
+    public boolean hasNotCompleted() {
+        return mAnimationProgress < ANIMATION_PROGRESS_COMPLETED;
+    }
+
     @Override
     public int getIntrinsicHeight() {
         return mIcon.getIntrinsicHeight();
diff --git a/src/com/android/launcher3/SearchDropTargetBar.java b/src/com/android/launcher3/SearchDropTargetBar.java
index 7c8f441..fdcad82 100644
--- a/src/com/android/launcher3/SearchDropTargetBar.java
+++ b/src/com/android/launcher3/SearchDropTargetBar.java
@@ -18,32 +18,58 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.View;
+import android.view.accessibility.AccessibilityManager;
 import android.view.animation.AccelerateInterpolator;
 import android.widget.FrameLayout;
 
+import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.util.Thunk;
+
 /*
  * Ths bar will manage the transition between the QSB search bar and the delete drop
  * targets so that each of the individual IconDropTargets don't have to.
  */
 public class SearchDropTargetBar extends FrameLayout implements DragController.DragListener {
 
-    private static final int TRANSITION_DURATION = 200;
+    /** The different states that the search bar space can be in. */
+    public enum State {
+        INVISIBLE   (0f, 0f),
+        SEARCH_BAR  (1f, 0f),
+        DROP_TARGET (0f, 1f);
 
-    private ObjectAnimator mShowDropTargetBarAnim;
-    private ValueAnimator mHideSearchBarAnim;
+        private final float mSearchBarAlpha;
+        private final float mDropTargetBarAlpha;
+
+        State(float sbAlpha, float dtbAlpha) {
+            mSearchBarAlpha = sbAlpha;
+            mDropTargetBarAlpha = dtbAlpha;
+        }
+
+        float getSearchBarAlpha() {
+            return mSearchBarAlpha;
+        }
+
+        float getDropTargetBarAlpha() {
+            return mDropTargetBarAlpha;
+        }
+    }
+
+    private static int DEFAULT_DRAG_FADE_DURATION = 175;
+
+    private LauncherViewPropertyAnimator mDropTargetBarAnimator;
+    private LauncherViewPropertyAnimator mQSBSearchBarAnimator;
     private static final AccelerateInterpolator sAccelerateInterpolator =
             new AccelerateInterpolator();
 
-    private boolean mIsSearchBarHidden;
-    private View mQSBSearchBar;
-    private View mDropTargetBar;
+    private State mState = State.SEARCH_BAR;
+    @Thunk View mQSB;
+    @Thunk View mDropTargetBar;
     private boolean mDeferOnDragEnd = false;
+    @Thunk boolean mAccessibilityEnabled = false;
 
     // Drop targets
     private ButtonDropTarget mInfoDropTarget;
@@ -75,39 +101,6 @@
         mUninstallDropTarget.setLauncher(launcher);
     }
 
-    public void setQsbSearchBar(View qsb) {
-        mQSBSearchBar = qsb;
-        if (mQSBSearchBar != null) {
-            mHideSearchBarAnim = LauncherAnimUtils.ofFloat(mQSBSearchBar, "alpha", 1f, 0f);
-            setupAnimation(mHideSearchBarAnim, mQSBSearchBar);
-        } else {
-            // Create a no-op animation of the search bar is null
-            mHideSearchBarAnim = ValueAnimator.ofFloat(0, 0);
-            mHideSearchBarAnim.setDuration(TRANSITION_DURATION);
-        }
-    }
-
-    private void prepareStartAnimation(View v) {
-        // Enable the hw layers before the animation starts (will be disabled in the onAnimationEnd
-        // callback below)
-        if (v != null) {
-            v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-        }
-    }
-
-    private void setupAnimation(ValueAnimator anim, final View v) {
-        anim.setInterpolator(sAccelerateInterpolator);
-        anim.setDuration(TRANSITION_DURATION);
-        anim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                if (v != null) {
-                    v.setLayerType(View.LAYER_TYPE_NONE, null);
-                }
-            }
-        });
-    }
-
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
@@ -124,72 +117,89 @@
 
         // Create the various fade animations
         mDropTargetBar.setAlpha(0f);
-        mShowDropTargetBarAnim = LauncherAnimUtils.ofFloat(mDropTargetBar, "alpha", 0f, 1f);
-        setupAnimation(mShowDropTargetBarAnim, mDropTargetBar);
+        mDropTargetBarAnimator = new LauncherViewPropertyAnimator(mDropTargetBar);
+        mDropTargetBarAnimator.setInterpolator(sAccelerateInterpolator);
+        mDropTargetBarAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                // Ensure that the view is visible for the animation
+                mDropTargetBar.setVisibility(View.VISIBLE);
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (mDropTargetBar != null) {
+                    AlphaUpdateListener.updateVisibility(mDropTargetBar, mAccessibilityEnabled);
+                }
+            }
+        });
     }
 
-    /**
-     * Finishes all the animations on the search and drop target bars.
-     */
-    public void finishAnimations() {
-        prepareStartAnimation(mDropTargetBar);
-        mShowDropTargetBarAnim.reverse();
-        prepareStartAnimation(mQSBSearchBar);
-        mHideSearchBarAnim.reverse();
-    }
+    public void setQsbSearchBar(View qsb) {
+        mQSB = qsb;
+        if (mQSB != null) {
+            // Update the search ber animation
+            mQSBSearchBarAnimator = new LauncherViewPropertyAnimator(mQSB);
+            mQSBSearchBarAnimator.setInterpolator(sAccelerateInterpolator);
+            mQSBSearchBarAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    // Ensure that the view is visible for the animation
+                    if (mQSB != null) {
+                        mQSB.setVisibility(View.VISIBLE);
+                    }
+                }
 
-    /**
-     * Shows the search bar.
-     */
-    public void showSearchBar(boolean animated) {
-        if (!mIsSearchBarHidden) return;
-        if (animated) {
-            prepareStartAnimation(mQSBSearchBar);
-            mHideSearchBarAnim.reverse();
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    if (mQSB != null) {
+                        AlphaUpdateListener.updateVisibility(mQSB, mAccessibilityEnabled);
+                    }
+                }
+            });
         } else {
-            mHideSearchBarAnim.cancel();
-            if (mQSBSearchBar != null) {
-                mQSBSearchBar.setAlpha(1f);
+            mQSBSearchBarAnimator = null;
+        }
+    }
+
+    /**
+     * Animates the current search bar state to a new state.  If the {@param duration} is 0, then
+     * the state is applied immediately.
+     */
+    public void animateToState(State newState, int duration) {
+        if (mState != newState) {
+            mState = newState;
+
+            // Update the accessibility state
+            AccessibilityManager am = (AccessibilityManager)
+                    getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+            mAccessibilityEnabled = am.isEnabled();
+
+            animateViewAlpha(mQSBSearchBarAnimator, mQSB, newState.getSearchBarAlpha(),
+                    duration);
+            animateViewAlpha(mDropTargetBarAnimator, mDropTargetBar, newState.getDropTargetBarAlpha(),
+                    duration);
+        }
+    }
+
+    /**
+     * Convenience method to animate the alpha of a view using hardware layers.
+     */
+    private void animateViewAlpha(LauncherViewPropertyAnimator animator, View v, float alpha,
+            int duration) {
+        if (v == null) {
+            return;
+        }
+
+        animator.cancel();
+        if (Float.compare(v.getAlpha(), alpha) != 0) {
+            if (duration > 0) {
+                animator.alpha(alpha).withLayer().setDuration(duration).start();
+            } else {
+                v.setAlpha(alpha);
+                AlphaUpdateListener.updateVisibility(v, mAccessibilityEnabled);
             }
         }
-        mIsSearchBarHidden = false;
-    }
-
-    /**
-     * Hides the search bar.  We only use this for clings.
-     */
-    public void hideSearchBar(boolean animated) {
-        if (mIsSearchBarHidden) return;
-        if (animated) {
-            prepareStartAnimation(mQSBSearchBar);
-            mHideSearchBarAnim.start();
-        } else {
-            mHideSearchBarAnim.cancel();
-            if (mQSBSearchBar != null) {
-                mQSBSearchBar.setAlpha(0f);
-            }
-        }
-        mIsSearchBarHidden = true;
-    }
-
-    /**
-     * Shows the drop target bar.
-     */
-    public void showDeleteTarget() {
-        // Animate out the QSB search bar, and animate in the drop target bar
-        prepareStartAnimation(mDropTargetBar);
-        mShowDropTargetBarAnim.start();
-        hideSearchBar(true);
-    }
-
-    /**
-     * Hides the drop target bar.
-     */
-    public void hideDeleteTarget() {
-        // Restore the QSB search bar, and animate out the drop target bar
-        prepareStartAnimation(mDropTargetBar);
-        mShowDropTargetBarAnim.reverse();
-        showSearchBar(true);
     }
 
     /*
@@ -197,9 +207,13 @@
      */
     @Override
     public void onDragStart(DragSource source, ItemInfo info, int dragAction) {
-        showDeleteTarget();
+        animateToState(State.DROP_TARGET, DEFAULT_DRAG_FADE_DURATION);
     }
 
+    /**
+     * This is called to defer hiding the delete drop target until the drop animation has completed,
+     * instead of hiding immediately when the drag has ended.
+     */
     public void deferOnDragEnd() {
         mDeferOnDragEnd = true;
     }
@@ -207,22 +221,25 @@
     @Override
     public void onDragEnd() {
         if (!mDeferOnDragEnd) {
-            hideDeleteTarget();
+            animateToState(State.SEARCH_BAR, DEFAULT_DRAG_FADE_DURATION);
         } else {
             mDeferOnDragEnd = false;
         }
     }
 
+    /**
+     * @return the bounds of the QSB search bar.
+     */
     public Rect getSearchBarBounds() {
-        if (mQSBSearchBar != null) {
+        if (mQSB != null) {
             final int[] pos = new int[2];
-            mQSBSearchBar.getLocationOnScreen(pos);
+            mQSB.getLocationOnScreen(pos);
 
             final Rect rect = new Rect();
             rect.left = pos[0];
             rect.top = pos[1];
-            rect.right = pos[0] + mQSBSearchBar.getWidth();
-            rect.bottom = pos[1] + mQSBSearchBar.getHeight();
+            rect.right = pos[0] + mQSB.getWidth();
+            rect.bottom = pos[1] + mQSB.getHeight();
             return rect;
         } else {
             return null;
@@ -230,8 +247,8 @@
     }
 
     public void enableAccessibleDrag(boolean enable) {
-        if (mQSBSearchBar != null) {
-            mQSBSearchBar.setVisibility(enable ? View.GONE : View.VISIBLE);
+        if (mQSB != null) {
+            mQSB.setVisibility(enable ? View.GONE : View.VISIBLE);
         }
         mInfoDropTarget.enableAccessibleDrag(enable);
         mDeleteDropTarget.enableAccessibleDrag(enable);
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 fe76634..a86a2f9 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -70,7 +70,7 @@
     /**
      * The intent used to start the application.
      */
-    Intent intent;
+    public Intent intent;
 
     /**
      * Indicates whether the icon comes from an application's resource (if false)
@@ -197,13 +197,17 @@
         return mIcon;
     }
 
-    public void updateIcon(IconCache iconCache) {
+    public void updateIcon(IconCache iconCache, boolean useLowRes) {
         if (itemType == Favorites.ITEM_TYPE_APPLICATION) {
             iconCache.getTitleAndIcon(this, promisedIntent != null ? promisedIntent : intent, user,
-                    shouldUseLowResIcon());
+                    useLowRes);
         }
     }
 
+    public void updateIcon(IconCache iconCache) {
+        updateIcon(iconCache, shouldUseLowResIcon());
+    }
+
     @Override
     void onAddToDatabase(Context context, ContentValues values) {
         super.onAddToDatabase(context, values);
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 da46e6a..54c5509 100644
--- a/src/com/android/launcher3/StylusEventHelper.java
+++ b/src/com/android/launcher3/StylusEventHelper.java
@@ -1,8 +1,5 @@
 package com.android.launcher3;
 
-import com.android.launcher3.Utilities;
-
-import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
diff --git a/src/com/android/launcher3/UninstallDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java
index 7c548a5..9ed4fb6 100644
--- a/src/com/android/launcher3/UninstallDropTarget.java
+++ b/src/com/android/launcher3/UninstallDropTarget.java
@@ -38,7 +38,7 @@
 
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
     public static boolean supportsDrop(Context context, Object info) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+        if (Utilities.ATLEAST_JB_MR2) {
             UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
             Bundle restrictions = userManager.getUserRestrictions();
             if (restrictions.getBoolean(UserManager.DISALLOW_APPS_CONTROL, false)
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 8fd298d..2340f95 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;
@@ -87,6 +90,24 @@
     private static final int[] sLoc0 = new int[2];
     private static final int[] sLoc1 = new int[2];
 
+    // TODO: use Build.VERSION_CODES when available
+    public static final boolean ATLEAST_MARSHMALLOW = Build.VERSION.SDK_INT >= 23;
+
+    public static final boolean ATLEAST_LOLLIPOP_MR1 =
+            Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1;
+
+    public static final boolean ATLEAST_LOLLIPOP =
+            Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
+
+    public static final boolean ATLEAST_KITKAT =
+            Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
+
+    public static final boolean ATLEAST_JB_MR1 =
+            Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1;
+
+    public static final boolean ATLEAST_JB_MR2 =
+            Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2;
+
     // To turn on these properties, type
     // adb shell setprop log.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
     private static final String FORCE_ENABLE_ROTATION_PROPERTY = "launcher_force_rotate";
@@ -110,21 +131,16 @@
         return sForceEnableRotation || context.getResources().getBoolean(R.bool.allow_rotation);
     }
 
-    /**
-     * Indicates if the device is running LMP or higher.
-     */
-    public static boolean isLmpOrAbove() {
-        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
-    }
-
-    public static boolean isLmpMR1OrAbove() {
-        // TODO(adamcohen): update to Build.VERSION_CODES.LOLLIPOP_MR1 once building against 22;
-        return Build.VERSION.SDK_INT >= 22;
-    }
-
-    public static boolean isLmpMR1() {
-        // TODO(adamcohen): update to Build.VERSION_CODES.LOLLIPOP_MR1 once building against 22;
-        return Build.VERSION.SDK_INT == 22;
+    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) {
@@ -339,15 +355,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);
@@ -368,11 +375,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(
@@ -515,16 +529,6 @@
         return null;
     }
 
-    @TargetApi(Build.VERSION_CODES.KITKAT)
-    public static boolean isViewAttachedToWindow(View v) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.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.
@@ -544,7 +548,7 @@
         AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
         for (AppWidgetProviderInfo info : appWidgetManager.getInstalledProviders()) {
             if (info.provider.getPackageName().equals(providerPkg)) {
-                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+                if (ATLEAST_JB_MR1) {
                     if ((info.widgetCategory & AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX) != 0) {
                         return info;
                     } else if (defaultWidgetForSearchPackage == null) {
@@ -655,12 +659,12 @@
 
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
     public static boolean isRtl(Resources res) {
-        return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) &&
+        return ATLEAST_JB_MR1 &&
                 (res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
     }
 
     public static void assertWorkerThread() {
-        if (LauncherAppState.isDogfoodBuild() &&
+        if (ProviderConfig.IS_DOGFOOD_BUILD &&
                 (LauncherModel.sWorkerThread.getThreadId() != Process.myTid())) {
             throw new IllegalStateException();
         }
@@ -709,4 +713,18 @@
     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;
+        }
+    }
 }
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/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index 629387e..3460555 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -105,7 +105,7 @@
      * sizes (landscape vs portrait).
      */
     private static class CacheDb extends SQLiteOpenHelper {
-        private static final int DB_VERSION = 3;
+        private static final int DB_VERSION = 4;
 
         private static final String TABLE_NAME = "shortcut_and_widget_previews";
         private static final String COLUMN_COMPONENT = "componentName";
@@ -212,7 +212,6 @@
     public void removeObsoletePreviews(ArrayList<Object> list) {
         Utilities.assertWorkerThread();
 
-        LongSparseArray<UserHandleCompat> userIdCache = new LongSparseArray<>();
         LongSparseArray<HashSet<String>> validPackages = new LongSparseArray<>();
 
         for (Object obj : list) {
@@ -227,15 +226,7 @@
                 pkg = info.provider.getPackageName();
             }
 
-            int userIdIndex = userIdCache.indexOfValue(user);
-            final long userId;
-            if (userIdIndex < 0) {
-                userId = mUserManager.getSerialNumberForUser(user);
-                userIdCache.put(userId, user);
-            } else {
-                userId = userIdCache.keyAt(userIdIndex);
-            }
-
+            final long userId = mUserManager.getSerialNumberForUser(user);
             HashSet<String> packages = validPackages.get(userId);
             if (packages == null) {
                 packages = new HashSet<>();
@@ -361,8 +352,8 @@
         }
 
         final boolean widgetPreviewExists = (drawable != null);
-        final int spanX = info.getSpanX(launcher) < 1 ? 1 : info.getSpanX(launcher);
-        final int spanY = info.getSpanY(launcher) < 1 ? 1 : info.getSpanY(launcher);
+        final int spanX = info.spanX;
+        final int spanY = info.spanY;
 
         int previewWidth;
         int previewHeight;
@@ -386,7 +377,7 @@
             preScaledWidthOut[0] = previewWidth;
         }
         if (previewWidth > maxPreviewWidth) {
-            scale = maxPreviewWidth / (float) previewWidth;
+            scale = (maxPreviewWidth - 2 * mProfileBadgeMargin) / (float) (previewWidth);
         }
         if (scale != 1f) {
             previewWidth = (int) (scale * previewWidth);
@@ -405,7 +396,7 @@
         }
 
         // Draw the scaled preview into the final bitmap
-        int x = (preview.getWidth() - previewWidth - mProfileBadgeMargin) / 2;
+        int x = (preview.getWidth() - previewWidth) / 2;
         if (widgetPreviewExists) {
             drawable.setBounds(x, 0, x + previewWidth, previewHeight);
             drawable.draw(c);
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index edf8a32..964a83d 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;
@@ -136,9 +139,6 @@
     private int mDragOverX = -1;
     private int mDragOverY = -1;
 
-    private static Rect mLandscapeCellLayoutMetrics = null;
-    private static Rect mPortraitCellLayoutMetrics = null;
-
     CustomContentCallbacks mCustomContentCallbacks;
     boolean mCustomContentShowing;
     private float mLastCustomContentScrollProgress = -1f;
@@ -177,7 +177,24 @@
     // State variable that indicates whether the pages are small (ie when you're
     // in all apps or customize mode)
 
-    enum State { NORMAL, NORMAL_HIDDEN, SPRING_LOADED, OVERVIEW, OVERVIEW_HIDDEN};
+    enum State {
+        NORMAL          (SearchDropTargetBar.State.SEARCH_BAR),
+        NORMAL_HIDDEN   (SearchDropTargetBar.State.INVISIBLE),
+        SPRING_LOADED   (SearchDropTargetBar.State.DROP_TARGET),
+        OVERVIEW        (SearchDropTargetBar.State.INVISIBLE),
+        OVERVIEW_HIDDEN (SearchDropTargetBar.State.INVISIBLE);
+
+        private final SearchDropTargetBar.State mBarState;
+
+        State(SearchDropTargetBar.State searchBarState) {
+            mBarState = searchBarState;
+        }
+
+        public SearchDropTargetBar.State getSearchDropTargetBarState() {
+            return mBarState;
+        }
+    };
+
     private State mState = State.NORMAL;
     private boolean mIsSwitchingState = false;
 
@@ -300,8 +317,9 @@
         TypedArray a = context.obtainStyledAttributes(attrs,
                 R.styleable.Workspace, defStyle, 0);
         mSpringLoadedShrinkFactor =
-            res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
-        mOverviewModeShrinkFactor = grid.getOverviewModeScale(mIsRtl);
+                res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
+        mOverviewModeShrinkFactor =
+                res.getInteger(R.integer.config_workspaceOverviewShrinkPercentage) / 100f;
         mOriginalDefaultPage = mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1);
         a.recycle();
 
@@ -859,6 +877,9 @@
             }
         }
 
+        LauncherAccessibilityDelegate delegate =
+                LauncherAppState.getInstance().getAccessibilityDelegate();
+
         // We enforce at least one page to add new items to. In the case that we remove the last
         // such screen, we convert the last screen to the empty screen
         int minScreens = 1 + numCustomPages();
@@ -873,6 +894,11 @@
                 if (indexOfChild(cl) < currentPage) {
                     pageShift++;
                 }
+
+                if (delegate != null && delegate.isInAccessibleDrag()) {
+                    cl.enableAccessibleDrag(false, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
+                }
+
                 removeView(cl);
             } else {
                 // if this is the last non-custom content screen, convert it to the empty screen
@@ -1282,12 +1308,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);
@@ -1556,7 +1582,7 @@
             // Reset our click listener
             setOnClickListener(mLauncher);
         }
-        mLauncher.getSearchBar().enableAccessibleDrag(enable);
+        mLauncher.getSearchDropTargetBar().enableAccessibleDrag(enable);
         mLauncher.getHotseat().getLayout()
             .enableAccessibleDrag(enable, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
     }
@@ -1956,15 +1982,17 @@
 
     int getOverviewModeTranslationY() {
         DeviceProfile grid = mLauncher.getDeviceProfile();
-        Rect overviewBar = grid.getOverviewModeButtonBarRect();
+        Rect workspacePadding = grid.getWorkspacePadding(Utilities.isRtl(getResources()));
+        int overviewButtonBarHeight = grid.getOverviewModeButtonBarHeight();
 
-        int availableHeight = getViewportHeight();
         int scaledHeight = (int) (mOverviewModeShrinkFactor * getNormalChildHeight());
-        int offsetFromTopEdge = (availableHeight - scaledHeight) / 2;
-        int offsetToCenterInOverview = (availableHeight - mInsets.top - overviewBar.height()
-                - scaledHeight) / 2;
-
-        return -offsetFromTopEdge + mInsets.top + offsetToCenterInOverview;
+        int workspaceTop = mInsets.top + workspacePadding.top;
+        int workspaceBottom = getViewportHeight() - mInsets.bottom - workspacePadding.bottom;
+        int overviewTop = mInsets.top;
+        int overviewBottom = getViewportHeight() - mInsets.bottom - overviewButtonBarHeight;
+        int workspaceOffsetTopEdge = workspaceTop + ((workspaceBottom - workspaceTop) - scaledHeight) / 2;
+        int overviewOffsetTopEdge = overviewTop + (overviewBottom - overviewTop - scaledHeight) / 2;
+        return -workspaceOffsetTopEdge + overviewOffsetTopEdge;
     }
 
     /**
@@ -1972,10 +2000,10 @@
      * to that new state.
      */
     public Animator setStateWithAnimation(State toState, boolean animated,
-            boolean hasOverlaySearchBar, HashMap<View, Integer> layerViews) {
+            HashMap<View, Integer> layerViews) {
         // Create the animation to the new state
         Animator workspaceAnim =  mStateTransitionAnimation.getAnimationToState(mState,
-                toState, animated, hasOverlaySearchBar, layerViews);
+                toState, animated, layerViews);
 
         // Update the current state
         mState = toState;
@@ -1993,7 +2021,7 @@
     }
 
     public void updateAccessibilityFlags() {
-        if (Utilities.isLmpOrAbove()) {
+        if (Utilities.ATLEAST_LOLLIPOP) {
             int total = getPageCount();
             for (int i = numCustomPages(); i < total; i++) {
                 updateAccessibilityFlags((CellLayout) getPageAt(i), i);
@@ -2102,19 +2130,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).
@@ -2300,15 +2326,15 @@
             throw new IllegalStateException(msg);
         }
 
+        if (child.getParent() instanceof ShortcutAndWidgetContainer) {
+            mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
+        }
+
         DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source,
                 (ItemInfo) dragObject, DragController.DRAG_ACTION_MOVE, dragVisualizeOffset,
                 dragRect, scale, accessible);
         dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
 
-        if (child.getParent() instanceof ShortcutAndWidgetContainer) {
-            mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
-        }
-
         b.recycle();
     }
 
@@ -2384,7 +2410,7 @@
             if (mLauncher.isHotseatLayout(dropTargetLayout)) {
                 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
             } else {
-                mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
+                mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter);
             }
 
             int spanX = 1;
@@ -2590,7 +2616,7 @@
             if (mLauncher.isHotseatLayout(dropTargetLayout)) {
                 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
             } else {
-                mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
+                mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter);
             }
         }
 
@@ -2675,7 +2701,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],
@@ -2821,45 +2847,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) {
@@ -2998,13 +2985,8 @@
     *
     * 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();
    }
@@ -3069,10 +3051,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(sTmpInvMatrix);
-            mapPointFromSelfToChild(cl, touchXy, sTmpInvMatrix);
+            mapPointFromSelfToChild(cl, touchXy);
 
             if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
                     touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) {
@@ -3117,7 +3096,7 @@
         CellLayout layout = null;
         ItemInfo item = d.dragInfo;
         if (item == null) {
-            if (LauncherAppState.isDogfoodBuild()) {
+            if (ProviderConfig.IS_DOGFOOD_BUILD) {
                 throw new NullPointerException("DragObject has null info");
             }
             return;
@@ -3173,7 +3152,7 @@
             if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
                 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
             } else {
-                mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null);
+                mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter);
             }
 
             ItemInfo info = d.dragInfo;
@@ -3725,7 +3704,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. ");
             };
@@ -3745,8 +3724,12 @@
         CellLayout parentCell = getParentCellLayoutForView(v);
         if (parentCell != null) {
             parentCell.removeView(v);
-        } else if (LauncherAppState.isDogfoodBuild()) {
-            throw new NullPointerException("mDragInfo.cell has null parent");
+        } 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
+            // {@link Launcher#mOnResumeCallbacks} depending on how busy the worker thread is.
+            Log.e(TAG, "mDragInfo.cell has null parent");
         }
         if (v instanceof DropTarget) {
             mDragController.removeDropTarget((DropTarget) v);
@@ -4010,6 +3993,16 @@
         });
     }
 
+    public View getHomescreenIconByItemId(final long id) {
+        return getFirstMatch(new ItemOperator() {
+
+            @Override
+            public boolean evaluate(ItemInfo info, View v, View parent) {
+                return info.id == id;
+            }
+        });
+    }
+
     public View getViewForTag(final Object tag) {
         return getFirstMatch(new ItemOperator() {
 
@@ -4258,8 +4251,9 @@
                         updates.contains(info)) {
                     ShortcutInfo si = (ShortcutInfo) info;
                     BubbleTextView shortcut = (BubbleTextView) v;
-                    boolean oldPromiseState = getTextViewIcon(shortcut)
-                            instanceof PreloadIconDrawable;
+                    Drawable oldIcon = getTextViewIcon(shortcut);
+                    boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable)
+                            && ((PreloadIconDrawable) oldIcon).hasNotCompleted();
                     shortcut.applyFromShortcutInfo(si, mIconCache,
                             si.isPromise() != oldPromiseState);
 
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 5d16cd5..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;
@@ -210,8 +211,9 @@
         mOverlayTransitionTime = res.getInteger(R.integer.config_overlayTransitionTime);
         mSpringLoadedShrinkFactor =
                 res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100f;
+        mOverviewModeShrinkFactor =
+                res.getInteger(R.integer.config_workspaceOverviewShrinkPercentage) / 100f;
         mWorkspaceScrimAlpha = res.getInteger(R.integer.config_workspaceScrimAlpha) / 100f;
-        mOverviewModeShrinkFactor = grid.getOverviewModeScale(Utilities.isRtl(res));
         mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
     }
 
@@ -220,14 +222,13 @@
     }
 
     public AnimatorSet getAnimationToState(Workspace.State fromState, Workspace.State toState,
-            boolean animated, boolean hasOverlaySearchBar, 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 duration = getAnimationDuration(states);
-        animateWorkspace(states, animated, duration, layerViews, accessibilityEnabled);
-        animateSearchBar(states, animated, duration, hasOverlaySearchBar, layerViews,
+        int workspaceDuration = getAnimationDuration(states);
+        animateWorkspace(states, animated, workspaceDuration, layerViews,
                 accessibilityEnabled);
         animateBackgroundGradient(states, animated, BACKGROUND_FADE_OUT_DURATION);
         return mStateAnimator;
@@ -378,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);
@@ -470,75 +470,9 @@
     }
 
     /**
-     * Coordinates with the workspace animation to animate the search bar.
-     *
-     * TODO: This should really be coordinated with the SearchDropTargetBar, otherwise the
-     *       bar has no idea that it is hidden, and this has no idea what state the bar is
-     *       actually in.
-     */
-    private void animateSearchBar(TransitionStates states, boolean animated, int duration,
-            boolean hasOverlaySearchBar, final HashMap<View, Integer> layerViews,
-            final boolean accessibilityEnabled) {
-
-        // The search bar is only visible in the workspace
-        final View searchBar = mLauncher.getOrCreateQsbBar();
-        if (searchBar != null) {
-            final boolean searchBarWillBeShown = states.stateIsNormal;
-            final float finalSearchBarAlpha = searchBarWillBeShown ? 1f : 0f;
-            if (animated) {
-                if (hasOverlaySearchBar) {
-                    // If there is an overlay search bar, then we will coordinate with it.
-                    mStateAnimator.addListener(new AnimatorListenerAdapter() {
-                        @Override
-                        public void onAnimationStart(Animator animation) {
-                            // If we are transitioning to a visible search bar, show it immediately
-                            // and let the overlay search bar has faded out
-                            if (searchBarWillBeShown) {
-                                searchBar.setAlpha(finalSearchBarAlpha);
-                                AlphaUpdateListener.updateVisibility(searchBar, accessibilityEnabled);
-                            }
-                        }
-
-                        @Override
-                        public void onAnimationEnd(Animator animation) {
-                            // If we are transitioning to a hidden search bar, hide it only after
-                            // the overlay search bar has faded in
-                            if (!searchBarWillBeShown) {
-                                searchBar.setAlpha(finalSearchBarAlpha);
-                                AlphaUpdateListener.updateVisibility(searchBar, accessibilityEnabled);
-                            }
-                        }
-                    });
-                } else {
-                    // Otherwise, we can just do the normal animation
-                    LauncherViewPropertyAnimator searchBarAlpha =
-                            new LauncherViewPropertyAnimator(searchBar).alpha(finalSearchBarAlpha);
-                    searchBarAlpha.addListener(new AlphaUpdateListener(searchBar,
-                            accessibilityEnabled));
-                    searchBar.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-                    if (layerViews != null) {
-                        // If layerViews is not null, we add these views, and indicate that
-                        // the caller can manage layer state.
-                        layerViews.put(searchBar, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
-                    } else {
-                        // Otherwise let the animator handle layer management.
-                        searchBarAlpha.withLayer();
-                    }
-                    searchBarAlpha.setDuration(duration);
-                    mStateAnimator.play(searchBarAlpha);
-                }
-            } else {
-                // Set the search bar state immediately
-                searchBar.setAlpha(finalSearchBarAlpha);
-                AlphaUpdateListener.updateVisibility(searchBar, accessibilityEnabled);
-            }
-        }
-    }
-
-    /**
      * Animates the background scrim. Add to the state animator to prevent jankiness.
      *
-     * @param finalAlpha the final alpha for the background scrim
+     * @param states the current and final workspace states
      * @param animated whether or not to set the background alpha immediately
      * @duration duration of the animation
      */
@@ -553,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 c11aab9..9479685 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -20,7 +20,6 @@
 import com.android.launcher3.AppWidgetResizeFrame;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeleteDropTarget;
-import com.android.launcher3.DragController.DragListener;
 import com.android.launcher3.DragSource;
 import com.android.launcher3.Folder;
 import com.android.launcher3.FolderInfo;
@@ -36,6 +35,7 @@
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.UninstallDropTarget;
 import com.android.launcher3.Workspace;
+import com.android.launcher3.dragndrop.DragController.DragListener;
 import com.android.launcher3.util.Thunk;
 
 import java.util.ArrayList;
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 32c9012..a172d02 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -16,34 +16,26 @@
 package com.android.launcher3.allapps;
 
 import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.InsetDrawable;
-import android.os.Build;
-import android.os.Bundle;
 import android.support.v7.widget.RecyclerView;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
 import android.text.method.TextKeyListener;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
-import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.widget.FrameLayout;
 import android.widget.LinearLayout;
-
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.BaseContainerView;
-import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.CellLayout;
-import com.android.launcher3.CheckLongPressHelper;
 import com.android.launcher3.DeleteDropTarget;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DragSource;
@@ -53,7 +45,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherTransitionable;
 import com.android.launcher3.R;
-import com.android.launcher3.Stats;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.util.ComponentKey;
@@ -155,6 +146,7 @@
     @Thunk AllAppsSearchBarController mSearchBarController;
     private ViewGroup mSearchBarContainerView;
     private View mSearchBarView;
+    private SpannableStringBuilder mSearchQueryBuilder = null;
 
     private int mSectionNamesMargin;
     private int mNumAppsPerRow;
@@ -165,7 +157,13 @@
     // This coordinate is relative to its parent
     private final Point mIconLastTouchPos = new Point();
 
-    private SpannableStringBuilder mSearchQueryBuilder = null;
+    private View.OnClickListener mSearchClickListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            Intent searchIntent = (Intent) v.getTag();
+            mLauncher.startActivitySafely(v, searchIntent, null);
+        }
+    };
 
     public AllAppsContainerView(Context context) {
         this(context, null);
@@ -182,8 +180,7 @@
         mLauncher = (Launcher) context;
         mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
         mApps = new AlphabeticalAppsList(context);
-        mAdapter = new AllAppsGridAdapter(context, mApps, this, mLauncher, this);
-        mAdapter.setEmptySearchText(res.getString(R.string.all_apps_loading_message));
+        mAdapter = new AllAppsGridAdapter(mLauncher, mApps, this, mLauncher, this);
         mApps.setAdapter(mAdapter);
         mLayoutManager = mAdapter.getLayoutManager();
         mItemDecoration = mAdapter.getItemDecoration();
@@ -528,7 +525,6 @@
                 CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
                 ItemInfo itemInfo = d.dragInfo;
                 if (layout != null) {
-                    layout.calculateSpans(itemInfo);
                     showOutOfSpaceMessage =
                             !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
                 }
@@ -559,8 +555,9 @@
     @Override
     public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
         if (toWorkspace) {
-            // Reset the search bar after transitioning home
+            // Reset the search bar and base recycler view after transitioning home
             mSearchBarController.reset();
+            mAppsRecyclerView.reset();
         }
     }
 
@@ -616,13 +613,9 @@
     @Override
     public void onSearchResult(String query, ArrayList<ComponentKey> apps) {
         if (apps != null) {
-            if (apps.isEmpty()) {
-                String formatStr = getResources().getString(R.string.all_apps_no_search_results);
-                mAdapter.setEmptySearchText(String.format(formatStr, query));
-            } else {
-                mAppsRecyclerView.scrollToTop();
-            }
             mApps.setOrderedFilter(apps);
+            mAdapter.setLastSearchQuery(query);
+            mAppsRecyclerView.scrollToTop();
         }
     }
 
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 057883c..affd32a 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -16,21 +16,28 @@
 package com.android.launcher3.allapps;
 
 import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.PointF;
 import android.graphics.Rect;
-import android.os.Handler;
+import android.support.v4.view.accessibility.AccessibilityRecordCompat;
+import android.support.v4.view.accessibility.AccessibilityEventCompat;
+import android.net.Uri;
 import android.support.v7.widget.GridLayoutManager;
 import android.support.v7.widget.RecyclerView;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
 import android.widget.TextView;
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.Thunk;
@@ -42,7 +49,7 @@
 /**
  * The grid view adapter of all the apps.
  */
-class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHolder> {
+public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHolder> {
 
     public static final String TAG = "AppsGridAdapter";
     private static final boolean DEBUG = false;
@@ -55,6 +62,10 @@
     public static final int PREDICTION_ICON_VIEW_TYPE = 2;
     // The message shown when there are no filtered results
     public static final int EMPTY_SEARCH_VIEW_TYPE = 3;
+    // A divider that separates the apps list and the search market button
+    public static final int SEARCH_MARKET_DIVIDER_VIEW_TYPE = 4;
+    // The message to continue to a market search when there are no filtered results
+    public static final int SEARCH_MARKET_VIEW_TYPE = 5;
 
     /**
      * ViewHolder for each icon.
@@ -69,6 +80,38 @@
     }
 
     /**
+     * A subclass of GridLayoutManager that overrides accessibility values during app search.
+     */
+    public class AppsGridLayoutManager extends GridLayoutManager {
+
+        public AppsGridLayoutManager(Context context) {
+            super(context, 1, GridLayoutManager.VERTICAL, false);
+        }
+
+        @Override
+        public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+            super.onInitializeAccessibilityEvent(event);
+
+            // Ensure that we only report the number apps for accessibility not including other
+            // adapter views
+            final AccessibilityRecordCompat record = AccessibilityEventCompat
+                    .asRecord(event);
+            record.setItemCount(mApps.getNumFilteredApps());
+        }
+
+        @Override
+        public int getRowCountForAccessibility(RecyclerView.Recycler recycler,
+                RecyclerView.State state) {
+            if (mApps.hasNoFilteredResults()) {
+                // Disregard the no-search-results text as a list item for accessibility
+                return 0;
+            } else {
+                return super.getRowCountForAccessibility(recycler, state);
+            }
+        }
+    }
+
+    /**
      * Helper class to size the grid items.
      */
     public class GridSpanSizer extends GridLayoutManager.SpanSizeLookup {
@@ -80,11 +123,6 @@
 
         @Override
         public int getSpanSize(int position) {
-            if (mApps.hasNoFilteredResults()) {
-                // Empty view spans full width
-                return mAppsPerRow;
-            }
-
             switch (mApps.getAdapterItems().get(position).viewType) {
                 case AllAppsGridAdapter.ICON_VIEW_TYPE:
                 case AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE:
@@ -279,6 +317,7 @@
         }
     }
 
+    @Thunk Launcher mLauncher;
     private LayoutInflater mLayoutInflater;
     @Thunk AlphabeticalAppsList mApps;
     private GridLayoutManager mGridLayoutMgr;
@@ -291,7 +330,19 @@
     @Thunk int mPredictionBarDividerOffset;
     @Thunk int mAppsPerRow;
     @Thunk boolean mIsRtl;
-    private String mEmptySearchText;
+
+    // The text to show when there are no search results and no market search handler.
+    private String mEmptySearchMessage;
+    // The name of the market app which handles searches, to be used in the format str
+    // below when updating the search-market view.  Only needs to be loaded once.
+    private String mMarketAppName;
+    // The text to show when there is a market app which can handle a specific query, updated
+    // 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.
+    @Thunk Intent mMarketSearchIntent;
+    // The last query that the user entered into the search field
+    @Thunk String mLastSearchQuery;
 
     // Section drawing
     @Thunk int mSectionNamesMargin;
@@ -299,16 +350,18 @@
     @Thunk Paint mSectionTextPaint;
     @Thunk Paint mPredictedAppsDividerPaint;
 
-    public AllAppsGridAdapter(Context context, AlphabeticalAppsList apps,
+    public AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps,
             View.OnTouchListener touchListener, View.OnClickListener iconClickListener,
             View.OnLongClickListener iconLongClickListener) {
-        Resources res = context.getResources();
+        Resources res = launcher.getResources();
+        mLauncher = launcher;
         mApps = apps;
+        mEmptySearchMessage = res.getString(R.string.all_apps_loading_message);
         mGridSizer = new GridSpanSizer();
-        mGridLayoutMgr = new GridLayoutManager(context, 1, GridLayoutManager.VERTICAL, false);
+        mGridLayoutMgr = new AppsGridLayoutManager(launcher);
         mGridLayoutMgr.setSpanSizeLookup(mGridSizer);
         mItemDecoration = new GridItemDecoration();
-        mLayoutInflater = LayoutInflater.from(context);
+        mLayoutInflater = LayoutInflater.from(launcher);
         mTouchListener = touchListener;
         mIconClickListener = iconClickListener;
         mIconLongClickListener = iconLongClickListener;
@@ -328,6 +381,14 @@
         mPredictionBarDividerOffset =
                 (-res.getDimensionPixelSize(R.dimen.all_apps_prediction_icon_bottom_padding) +
                         res.getDimensionPixelSize(R.dimen.all_apps_icon_top_bottom_padding)) / 2;
+
+        // Resolve the market app handling additional searches
+        PackageManager pm = launcher.getPackageManager();
+        ResolveInfo marketInfo = pm.resolveActivity(createMarketSearchIntent(""),
+                PackageManager.MATCH_DEFAULT_ONLY);
+        if (marketInfo != null) {
+            mMarketAppName = marketInfo.loadLabel(pm).toString();
+        }
     }
 
     /**
@@ -346,10 +407,19 @@
     }
 
     /**
-     * Sets the text to show when there are no apps.
+     * Sets the last search query that was made, used to show when there are no results and to also
+     * seed the intent for searching the market.
      */
-    public void setEmptySearchText(String query) {
-        mEmptySearchText = query;
+    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);
+        if (mMarketAppName != null) {
+            mMarketSearchMessage = String.format(res.getString(R.string.all_apps_search_market_message),
+                    mMarketAppName);
+            mMarketSearchIntent = createMarketSearchIntent(query);
+        }
     }
 
     /**
@@ -378,9 +448,6 @@
     @Override
     public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
         switch (viewType) {
-            case EMPTY_SEARCH_VIEW_TYPE:
-                return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_empty_search, parent,
-                        false));
             case SECTION_BREAK_VIEW_TYPE:
                 return new ViewHolder(new View(parent.getContext()));
             case ICON_VIEW_TYPE: {
@@ -405,6 +472,22 @@
                 icon.setFocusable(true);
                 return new ViewHolder(icon);
             }
+            case EMPTY_SEARCH_VIEW_TYPE:
+                return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_empty_search,
+                        parent, false));
+            case SEARCH_MARKET_DIVIDER_VIEW_TYPE:
+                return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_search_market_divider,
+                        parent, false));
+            case SEARCH_MARKET_VIEW_TYPE:
+                View searchMarketView = mLayoutInflater.inflate(R.layout.all_apps_search_market,
+                        parent, false);
+                searchMarketView.setOnClickListener(new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        mLauncher.startSearchFromAllApps(v, mMarketSearchIntent, mLastSearchQuery);
+                    }
+                });
+                return new ViewHolder(searchMarketView);
             default:
                 throw new RuntimeException("Unexpected view type");
         }
@@ -426,28 +509,44 @@
                 break;
             }
             case EMPTY_SEARCH_VIEW_TYPE:
-                TextView emptyViewText = (TextView) holder.mContent.findViewById(R.id.empty_text);
-                emptyViewText.setText(mEmptySearchText);
+                TextView emptyViewText = (TextView) holder.mContent;
+                emptyViewText.setText(mEmptySearchMessage);
+                break;
+            case SEARCH_MARKET_VIEW_TYPE:
+                View searchView = holder.mContent;
+                if (mMarketSearchIntent != null) {
+                    searchView.setVisibility(View.VISIBLE);
+                    searchView.setContentDescription(mMarketSearchMessage);
+                    ((TextView) searchView.findViewById(R.id.search_market_text))
+                            .setText(mMarketSearchMessage);
+                } else {
+                    searchView.setVisibility(View.GONE);
+                }
                 break;
         }
     }
 
     @Override
     public int getItemCount() {
-        if (mApps.hasNoFilteredResults()) {
-            // For the empty view
-            return 1;
-        }
         return mApps.getAdapterItems().size();
     }
 
     @Override
     public int getItemViewType(int position) {
-        if (mApps.hasNoFilteredResults()) {
-            return EMPTY_SEARCH_VIEW_TYPE;
-        }
-
         AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position);
         return item.viewType;
     }
+
+    /**
+     * Creates a new market search intent.
+     */
+    private Intent createMarketSearchIntent(String query) {
+        Uri marketSearchUri = Uri.parse("market://search")
+                .buildUpon()
+                .appendQueryParameter("q", query)
+                .build();
+        Intent marketSearchIntent = new Intent(Intent.ACTION_VIEW);
+        marketSearchIntent.setData(marketSearchUri);
+        return marketSearchIntent;
+    }
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 730c8d1..5ec8bb2 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -72,6 +72,7 @@
     public AllAppsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr);
+        mScrollbar.setDetachThumbOnFastScroll();
     }
 
     /**
@@ -90,6 +91,8 @@
         RecyclerView.RecycledViewPool pool = getRecycledViewPool();
         int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx);
         pool.setMaxRecycledViews(AllAppsGridAdapter.EMPTY_SEARCH_VIEW_TYPE, 1);
+        pool.setMaxRecycledViews(AllAppsGridAdapter.SEARCH_MARKET_DIVIDER_VIEW_TYPE, 1);
+        pool.setMaxRecycledViews(AllAppsGridAdapter.SEARCH_MARKET_VIEW_TYPE, 1);
         pool.setMaxRecycledViews(AllAppsGridAdapter.ICON_VIEW_TYPE, approxRows * mNumAppsPerRow);
         pool.setMaxRecycledViews(AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE, mNumAppsPerRow);
         pool.setMaxRecycledViews(AllAppsGridAdapter.SECTION_BREAK_VIEW_TYPE, approxRows);
@@ -166,8 +169,8 @@
         }
 
         // Map the touch position back to the scroll of the recycler view
-        getCurScrollState(mScrollPosState, mApps.getAdapterItems());
-        int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight, 0);
+        getCurScrollState(mScrollPosState);
+        int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight);
         LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
         if (mFastScrollMode == FAST_SCROLL_MODE_FREE_SCROLL) {
             layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction));
@@ -214,24 +217,73 @@
      * Updates the bounds for the scrollbar.
      */
     @Override
-    public void onUpdateScrollbar() {
+    public void onUpdateScrollbar(int dy) {
         List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
 
         // Skip early if there are no items or we haven't been measured
         if (items.isEmpty() || mNumAppsPerRow == 0) {
-            mScrollbar.setScrollbarThumbOffset(-1, -1);
+            mScrollbar.setThumbOffset(-1, -1);
             return;
         }
 
         // Find the index and height of the first visible row (all rows have the same height)
         int rowCount = mApps.getNumAppRows();
-        getCurScrollState(mScrollPosState, items);
+        getCurScrollState(mScrollPosState);
         if (mScrollPosState.rowIndex < 0) {
-            mScrollbar.setScrollbarThumbOffset(-1, -1);
+            mScrollbar.setThumbOffset(-1, -1);
             return;
         }
 
-        synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount, 0);
+        // Only show the scrollbar if there is height to be scrolled
+        int availableScrollBarHeight = getAvailableScrollBarHeight();
+        int availableScrollHeight = getAvailableScrollHeight(mApps.getNumAppRows(), mScrollPosState.rowHeight);
+        if (availableScrollHeight <= 0) {
+            mScrollbar.setThumbOffset(-1, -1);
+            return;
+        }
+
+        // Calculate the current scroll position, the scrollY of the recycler view accounts for the
+        // view padding, while the scrollBarY is drawn right up to the background padding (ignoring
+        // padding)
+        int scrollY = getPaddingTop() +
+                (mScrollPosState.rowIndex * mScrollPosState.rowHeight) - mScrollPosState.rowTopOffset;
+        int scrollBarY = mBackgroundPadding.top +
+                (int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
+
+        if (mScrollbar.isThumbDetached()) {
+            int scrollBarX;
+            if (Utilities.isRtl(getResources())) {
+                scrollBarX = mBackgroundPadding.left;
+            } else {
+                scrollBarX = getWidth() - mBackgroundPadding.right - mScrollbar.getThumbWidth();
+            }
+
+            if (mScrollbar.isDraggingThumb()) {
+                // If the thumb is detached, then just update the thumb to the current
+                // touch position
+                mScrollbar.setThumbOffset(scrollBarX, (int) mScrollbar.getLastTouchY());
+            } else {
+                int thumbScrollY = mScrollbar.getThumbOffset().y;
+                int diffScrollY = scrollBarY - thumbScrollY;
+                if (diffScrollY * dy > 0f) {
+                    // User is scrolling in the same direction the thumb needs to catch up to the
+                    // current scroll position.
+                    thumbScrollY += dy < 0 ? Math.max(dy, diffScrollY) : Math.min(dy, diffScrollY);
+                    thumbScrollY = Math.max(0, Math.min(availableScrollBarHeight, thumbScrollY));
+                    mScrollbar.setThumbOffset(scrollBarX, thumbScrollY);
+                    if (scrollBarY == thumbScrollY) {
+                        mScrollbar.reattachThumbToScroll();
+                    }
+                } else {
+                    // User is scrolling in an opposite direction to the direction that the thumb
+                    // needs to catch up to the scroll position.  Do nothing except for updating
+                    // the scroll bar x to match the thumb width.
+                    mScrollbar.setThumbOffset(scrollBarX, thumbScrollY);
+                }
+            }
+        } else {
+            synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount);
+        }
     }
 
     /**
@@ -283,13 +335,13 @@
     /**
      * Returns the current scroll state of the apps rows.
      */
-    private void getCurScrollState(ScrollPositionState stateOut,
-            List<AlphabeticalAppsList.AdapterItem> items) {
+    protected void getCurScrollState(ScrollPositionState stateOut) {
         stateOut.rowIndex = -1;
         stateOut.rowTopOffset = -1;
         stateOut.rowHeight = -1;
 
         // Return early if there are no items or we haven't been measured
+        List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
         if (items.isEmpty() || mNumAppsPerRow == 0) {
             return;
         }
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 47241ce..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;
 
@@ -43,6 +43,11 @@
     private static final boolean DEBUG = false;
     private static final boolean DEBUG_PREDICTIONS = false;
 
+    private static final int FAST_SCROLL_FRACTION_DISTRIBUTE_BY_ROWS_FRACTION = 0;
+    private static final int FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS = 1;
+
+    private final int mFastScrollDistributionMode = FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS;
+
     /**
      * Info about a section in the alphabetic list
      */
@@ -81,8 +86,6 @@
         public int position;
         // The type of this item
         public int viewType;
-        // The row that this item shows up on
-        public int rowIndex;
 
         /** Section & App properties */
         // The section for this item
@@ -94,6 +97,8 @@
         public String sectionName = null;
         // The index of this app in the section
         public int sectionAppIndex = -1;
+        // The row that this item shows up on
+        public int rowIndex;
         // The index of this app in the row
         public int rowAppIndex;
         // The associated AppInfo for the app
@@ -111,14 +116,14 @@
         }
 
         public static AdapterItem asPredictedApp(int pos, SectionInfo section, String sectionName,
-                                        int sectionAppIndex, AppInfo appInfo, int appIndex) {
+                int sectionAppIndex, AppInfo appInfo, int appIndex) {
             AdapterItem item = asApp(pos, section, sectionName, sectionAppIndex, appInfo, appIndex);
             item.viewType = AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE;
             return item;
         }
 
         public static AdapterItem asApp(int pos, SectionInfo section, String sectionName,
-                                        int sectionAppIndex, AppInfo appInfo, int appIndex) {
+                int sectionAppIndex, AppInfo appInfo, int appIndex) {
             AdapterItem item = new AdapterItem();
             item.viewType = AllAppsGridAdapter.ICON_VIEW_TYPE;
             item.position = pos;
@@ -129,6 +134,27 @@
             item.appIndex = appIndex;
             return item;
         }
+
+        public static AdapterItem asEmptySearch(int pos) {
+            AdapterItem item = new AdapterItem();
+            item.viewType = AllAppsGridAdapter.EMPTY_SEARCH_VIEW_TYPE;
+            item.position = pos;
+            return item;
+        }
+
+        public static AdapterItem asDivider(int pos) {
+            AdapterItem item = new AdapterItem();
+            item.viewType = AllAppsGridAdapter.SEARCH_MARKET_DIVIDER_VIEW_TYPE;
+            item.position = pos;
+            return item;
+        }
+
+        public static AdapterItem asMarketSearch(int pos) {
+            AdapterItem item = new AdapterItem();
+            item.viewType = AllAppsGridAdapter.SEARCH_MARKET_VIEW_TYPE;
+            item.position = pos;
+            return item;
+        }
     }
 
     /**
@@ -160,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;
@@ -189,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;
     }
 
@@ -222,13 +248,6 @@
     }
 
     /**
-     * Returns the number of applications in this list.
-     */
-    public int getSize() {
-        return mFilteredApps.size();
-    }
-
-    /**
      * Returns the number of rows of applications (not including predictions)
      */
     public int getNumAppRows() {
@@ -236,6 +255,13 @@
     }
 
     /**
+     * Returns the number of applications in this list.
+     */
+    public int getNumFilteredApps() {
+        return mFilteredApps.size();
+    }
+
+    /**
      * Returns whether there are is a filter set.
      */
     public boolean hasFilter() {
@@ -393,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));
                     }
                 }
@@ -457,6 +483,16 @@
             mFilteredApps.add(info);
         }
 
+        // Append the search market item if we are currently searching
+        if (hasFilter()) {
+            if (hasNoFilteredResults()) {
+                mAdapterItems.add(AdapterItem.asEmptySearch(position++));
+            } else {
+                mAdapterItems.add(AdapterItem.asDivider(position++));
+            }
+            mAdapterItems.add(AdapterItem.asMarketSearch(position++));
+        }
+
         // Merge multiple sections together as requested by the merge strategy for this device
         mergeSections();
 
@@ -484,18 +520,36 @@
             }
             mNumAppRowsInAdapter = rowIndex + 1;
 
-            // Pre-calculate all the fast scroller fractions based on the number of rows
-            float rowFraction = 1f / mNumAppRowsInAdapter;
-            for (FastScrollSectionInfo info : mFastScrollerSections) {
-                AdapterItem item = info.fastScrollToItem;
-                if (item.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE &&
-                        item.viewType != AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) {
-                    info.touchFraction = 0f;
-                    continue;
-                }
+            // Pre-calculate all the fast scroller fractions
+            switch (mFastScrollDistributionMode) {
+                case FAST_SCROLL_FRACTION_DISTRIBUTE_BY_ROWS_FRACTION:
+                    float rowFraction = 1f / mNumAppRowsInAdapter;
+                    for (FastScrollSectionInfo info : mFastScrollerSections) {
+                        AdapterItem item = info.fastScrollToItem;
+                        if (item.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE &&
+                                item.viewType != AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) {
+                            info.touchFraction = 0f;
+                            continue;
+                        }
 
-                float subRowFraction = item.rowAppIndex * (rowFraction / mNumAppsPerRow);
-                info.touchFraction = item.rowIndex * rowFraction + subRowFraction;
+                        float subRowFraction = item.rowAppIndex * (rowFraction / mNumAppsPerRow);
+                        info.touchFraction = item.rowIndex * rowFraction + subRowFraction;
+                    }
+                    break;
+                case FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS:
+                    float perSectionTouchFraction = 1f / mFastScrollerSections.size();
+                    float cumulativeTouchFraction = 0f;
+                    for (FastScrollSectionInfo info : mFastScrollerSections) {
+                        AdapterItem item = info.fastScrollToItem;
+                        if (item.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE &&
+                                item.viewType != AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) {
+                            info.touchFraction = 0f;
+                            continue;
+                        }
+                        info.touchFraction = cumulativeTouchFraction;
+                        cumulativeTouchFraction += perSectionTouchFraction;
+                    }
+                    break;
             }
         }
 
diff --git a/src/com/android/launcher3/allapps/DefaultAppSearchController.java b/src/com/android/launcher3/allapps/DefaultAppSearchController.java
index 83b9205..3169f84 100644
--- a/src/com/android/launcher3/allapps/DefaultAppSearchController.java
+++ b/src/com/android/launcher3/allapps/DefaultAppSearchController.java
@@ -25,6 +25,7 @@
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.TextView;
+import com.android.launcher3.ExtendedEditText;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.Thunk;
@@ -54,7 +55,8 @@
     @Thunk View mSearchBarContainerView;
     private View mSearchButtonView;
     private View mDismissSearchButtonView;
-    @Thunk AllAppsSearchEditView mSearchBarEditView;
+    @Thunk
+    ExtendedEditText mSearchBarEditView;
     @Thunk AllAppsRecyclerView mAppsRecyclerView;
     @Thunk Runnable mFocusRecyclerViewRunnable = new Runnable() {
         @Override
@@ -82,21 +84,23 @@
         mSearchBarContainerView = mSearchView.findViewById(R.id.search_container);
         mDismissSearchButtonView = mSearchBarContainerView.findViewById(R.id.dismiss_search_button);
         mDismissSearchButtonView.setOnClickListener(this);
-        mSearchBarEditView = (AllAppsSearchEditView)
+        mSearchBarEditView = (ExtendedEditText)
                 mSearchBarContainerView.findViewById(R.id.search_box_input);
         mSearchBarEditView.addTextChangedListener(this);
         mSearchBarEditView.setOnEditorActionListener(this);
         mSearchBarEditView.setOnBackKeyListener(
-                new AllAppsSearchEditView.OnBackKeyListener() {
+                new ExtendedEditText.OnBackKeyListener() {
                     @Override
-                    public void onBackKey() {
+                    public boolean onBackKey() {
                         // Only hide the search field if there is no query, or if there
                         // are no filtered results
                         String query = Utilities.trim(
                                 mSearchBarEditView.getEditableText().toString());
                         if (query.isEmpty() || mApps.hasNoFilteredResults()) {
                             hideSearchField(true, mFocusRecyclerViewRunnable);
+                            return true;
                         }
+                        return false;
                     }
                 });
         return mSearchView;
@@ -166,22 +170,24 @@
             return false;
         }
         // Skip if it's not the right action
-        if (actionId != EditorInfo.IME_ACTION_DONE) {
+        if (actionId != EditorInfo.IME_ACTION_SEARCH) {
             return false;
         }
-        // Skip if there isn't exactly one item
-        if (mApps.getSize() != 1) {
+        // Skip if there are more than one icon
+        if (mApps.getNumFilteredApps() > 1) {
             return false;
         }
-        // If there is exactly one icon, then quick-launch it
+        // Otherwise, find the first icon, or fallback to the search-market-view and launch it
         List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
         for (int i = 0; i < items.size(); i++) {
             AlphabeticalAppsList.AdapterItem item = items.get(i);
-            if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE) {
-                mAppsRecyclerView.getChildAt(i).performClick();
-                mInputMethodManager.hideSoftInputFromWindow(
-                        mContainerView.getWindowToken(), 0);
-                return true;
+            switch (item.viewType) {
+                case AllAppsGridAdapter.ICON_VIEW_TYPE:
+                case AllAppsGridAdapter.SEARCH_MARKET_VIEW_TYPE:
+                    mAppsRecyclerView.getChildAt(i).performClick();
+                    mInputMethodManager.hideSoftInputFromWindow(
+                            mContainerView.getWindowToken(), 0);
+                    return true;
             }
         }
         return false;
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/compat/AppWidgetManagerCompat.java b/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
index 7aa36d4..434f13d 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
@@ -40,7 +40,7 @@
     public static AppWidgetManagerCompat getInstance(Context context) {
         synchronized (sInstanceLock) {
             if (sInstance == null) {
-                if (Utilities.isLmpOrAbove()) {
+                if (Utilities.ATLEAST_LOLLIPOP) {
                     sInstance = new AppWidgetManagerCompatVL(context.getApplicationContext());
                 } else {
                     sInstance = new AppWidgetManagerCompatV16(context.getApplicationContext());
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java b/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java
index f7f4b7e..463cf90 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java
@@ -54,10 +54,10 @@
     @Override
     public boolean bindAppWidgetIdIfAllowed(int appWidgetId, AppWidgetProviderInfo info,
             Bundle options) {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            return mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, info.provider);
-        } else {
+        if (Utilities.ATLEAST_JB_MR1) {
             return mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, info.provider, options);
+        } else {
+            return mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, info.provider);
         }
     }
 
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompat.java b/src/com/android/launcher3/compat/LauncherAppsCompat.java
index 5858bc8..95e3ba9 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompat.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompat.java
@@ -53,7 +53,7 @@
     public static LauncherAppsCompat getInstance(Context context) {
         synchronized (sInstanceLock) {
             if (sInstance == null) {
-                if (Utilities.isLmpOrAbove()) {
+                if (Utilities.ATLEAST_LOLLIPOP) {
                     sInstance = new LauncherAppsCompatVL(context.getApplicationContext());
                 } else {
                     sInstance = new LauncherAppsCompatV16(context.getApplicationContext());
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatV16.java b/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
index ac3d252..339c457 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
@@ -31,6 +31,7 @@
 import android.os.Bundle;
 import android.provider.Settings;
 
+import com.android.launcher3.Utilities;
 import com.android.launcher3.util.Thunk;
 
 import java.util.ArrayList;
@@ -188,7 +189,7 @@
                 // when moving a package or mounting/un-mounting external storage. Assume that
                 // it is a replacing operation.
                 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING,
-                        Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT);
+                        !Utilities.ATLEAST_KITKAT);
                 String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
                 for (OnAppsChangedCallbackCompat callback : getCallbacks()) {
                     callback.onPackagesAvailable(packages, user, replacing);
diff --git a/src/com/android/launcher3/compat/PackageInstallerCompat.java b/src/com/android/launcher3/compat/PackageInstallerCompat.java
index c499083..ec5014d 100644
--- a/src/com/android/launcher3/compat/PackageInstallerCompat.java
+++ b/src/com/android/launcher3/compat/PackageInstallerCompat.java
@@ -34,7 +34,7 @@
     public static PackageInstallerCompat getInstance(Context context) {
         synchronized (sInstanceLock) {
             if (sInstance == null) {
-                if (Utilities.isLmpOrAbove()) {
+                if (Utilities.ATLEAST_LOLLIPOP) {
                     sInstance = new PackageInstallerCompatVL(context);
                 } else {
                     sInstance = new PackageInstallerCompatV16();
diff --git a/src/com/android/launcher3/compat/UserHandleCompat.java b/src/com/android/launcher3/compat/UserHandleCompat.java
index ab4b721..567022b 100644
--- a/src/com/android/launcher3/compat/UserHandleCompat.java
+++ b/src/com/android/launcher3/compat/UserHandleCompat.java
@@ -34,7 +34,7 @@
 
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
     public static UserHandleCompat myUserHandle() {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+        if (Utilities.ATLEAST_JB_MR1) {
             return new UserHandleCompat(android.os.Process.myUserHandle());
         } else {
             return new UserHandleCompat();
@@ -55,7 +55,7 @@
 
     @Override
     public String toString() {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+        if (Utilities.ATLEAST_JB_MR1) {
             return mUser.toString();
         } else {
             return "";
@@ -67,7 +67,7 @@
         if (!(other instanceof UserHandleCompat)) {
             return false;
         }
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+        if (Utilities.ATLEAST_JB_MR1) {
             return mUser.equals(((UserHandleCompat) other).mUser);
         } else {
             return true;
@@ -76,7 +76,7 @@
 
     @Override
     public int hashCode() {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+        if (Utilities.ATLEAST_JB_MR1) {
             return mUser.hashCode();
         } else {
             return 0;
@@ -89,7 +89,7 @@
      * profiles so this is a no-op.
      */
     public void addToIntent(Intent intent, String name) {
-        if (Utilities.isLmpOrAbove() && mUser != null) {
+        if (Utilities.ATLEAST_LOLLIPOP && mUser != null) {
             intent.putExtra(name, mUser);
         }
     }
diff --git a/src/com/android/launcher3/compat/UserManagerCompat.java b/src/com/android/launcher3/compat/UserManagerCompat.java
index a79d946..f708004 100644
--- a/src/com/android/launcher3/compat/UserManagerCompat.java
+++ b/src/com/android/launcher3/compat/UserManagerCompat.java
@@ -28,16 +28,29 @@
     protected UserManagerCompat() {
     }
 
+    private static final Object sInstanceLock = new Object();
+    private static UserManagerCompat sInstance;
+
     public static UserManagerCompat getInstance(Context context) {
-        if (Utilities.isLmpOrAbove()) {
-            return new UserManagerCompatVL(context);
-        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            return new UserManagerCompatV17(context);
-        } else {
-            return new UserManagerCompatV16();
+        synchronized (sInstanceLock) {
+            if (sInstance == null) {
+                if (Utilities.ATLEAST_LOLLIPOP) {
+                    sInstance = new UserManagerCompatVL(context.getApplicationContext());
+                } else if (Utilities.ATLEAST_JB_MR1) {
+                    sInstance = new UserManagerCompatV17(context.getApplicationContext());
+                } else {
+                    sInstance = new UserManagerCompatV16();
+                }
+            }
+            return sInstance;
         }
     }
 
+    /**
+     * Creates a cache for users.
+     */
+    public abstract void enableAndResetCache();
+
     public abstract List<UserHandleCompat> getUserProfiles();
     public abstract long getSerialNumberForUser(UserHandleCompat user);
     public abstract UserHandleCompat getUserForSerialNumber(long serialNumber);
diff --git a/src/com/android/launcher3/compat/UserManagerCompatV16.java b/src/com/android/launcher3/compat/UserManagerCompatV16.java
index ffe698c..85aee57 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatV16.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatV16.java
@@ -53,4 +53,8 @@
     public long getUserCreationTime(UserHandleCompat user) {
         return 0;
     }
+
+    @Override
+    public void enableAndResetCache() {
+    }
 }
diff --git a/src/com/android/launcher3/compat/UserManagerCompatV17.java b/src/com/android/launcher3/compat/UserManagerCompatV17.java
index c42c00c..75203b7 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatV17.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatV17.java
@@ -21,8 +21,18 @@
 import android.os.Build;
 import android.os.UserManager;
 
+import com.android.launcher3.util.LongArrayMap;
+
+import java.util.HashMap;
+
 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
 public class UserManagerCompatV17 extends UserManagerCompatV16 {
+
+    protected LongArrayMap<UserHandleCompat> mUsers;
+    // Create a separate reverse map as LongArrayMap.indexOfValue checks if objects are same
+    // and not {@link Object#equals}
+    protected HashMap<UserHandleCompat, Long> mUserToSerialMap;
+
     protected UserManager mUserManager;
 
     UserManagerCompatV17(Context context) {
@@ -30,11 +40,34 @@
     }
 
     public long getSerialNumberForUser(UserHandleCompat user) {
+        synchronized (this) {
+            if (mUserToSerialMap != null) {
+                Long serial = mUserToSerialMap.get(user);
+                return serial == null ? 0 : serial;
+            }
+        }
         return mUserManager.getSerialNumberForUser(user.getUser());
     }
 
     public UserHandleCompat getUserForSerialNumber(long serialNumber) {
+        synchronized (this) {
+            if (mUsers != null) {
+                return mUsers.get(serialNumber);
+            }
+        }
         return UserHandleCompat.fromUser(mUserManager.getUserForSerialNumber(serialNumber));
     }
+
+    @Override
+    public void enableAndResetCache() {
+        synchronized (this) {
+            mUsers = new LongArrayMap<>();
+            mUserToSerialMap = new HashMap<>();
+            UserHandleCompat myUser = UserHandleCompat.myUserHandle();
+            long serial = mUserManager.getSerialNumberForUser(myUser.getUser());
+            mUsers.put(serial, myUser);
+            mUserToSerialMap.put(myUser, serial);
+        }
+    }
 }
 
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVL.java b/src/com/android/launcher3/compat/UserManagerCompatVL.java
index dd7a726..4d404db 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatVL.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatVL.java
@@ -24,9 +24,13 @@
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.UserHandle;
+
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.util.LongArrayMap;
+
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 
 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
@@ -43,7 +47,32 @@
     }
 
     @Override
+    public void enableAndResetCache() {
+        synchronized (this) {
+            mUsers = new LongArrayMap<>();
+            mUserToSerialMap = new HashMap<>();
+            List<UserHandle> users = mUserManager.getUserProfiles();
+            if (users != null) {
+                for (UserHandle user : users) {
+                    long serial = mUserManager.getSerialNumberForUser(user);
+                    UserHandleCompat userCompat = UserHandleCompat.fromUser(user);
+                    mUsers.put(serial, userCompat);
+                    mUserToSerialMap.put(userCompat, serial);
+                }
+            }
+        }
+    }
+
+    @Override
     public List<UserHandleCompat> getUserProfiles() {
+        synchronized (this) {
+            if (mUsers != null) {
+                List<UserHandleCompat> users = new ArrayList<>();
+                users.addAll(mUserToSerialMap.keySet());
+                return users;
+            }
+        }
+
         List<UserHandle> users = mUserManager.getUserProfiles();
         if (users == null) {
             return Collections.emptyList();
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 410271d..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;
 
     /**
@@ -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.
      *
@@ -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 aaa14e6..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;
 
 /**
@@ -159,7 +175,7 @@
     }
 
     private boolean isEventOverDropTargetBar(MotionEvent ev) {
-        getDescendantRectRelativeToSelf(mLauncher.getSearchBar(), mHitRect);
+        getDescendantRectRelativeToSelf(mLauncher.getSearchDropTargetBar(), mHitRect);
         if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
             return true;
         }
@@ -321,7 +337,7 @@
             childrenForAccessibility.add(currentFolder);
 
             if (isInAccessibleDrag()) {
-                childrenForAccessibility.add(mLauncher.getSearchBar());
+                childrenForAccessibility.add(mLauncher.getSearchDropTargetBar());
             }
         } else {
             super.addChildrenForAccessibility(childrenForAccessibility);
@@ -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 82%
rename from src/com/android/launcher3/DragView.java
rename to src/com/android/launcher3/dragndrop/DragView.java
index dfa8202..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();
                 }
             }
         });
@@ -131,7 +139,7 @@
         measure(ms, ms);
         mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
 
-        if (Utilities.isLmpOrAbove()) {
+        if (Utilities.ATLEAST_LOLLIPOP) {
             setElevation(getResources().getDimension(R.dimen.drag_elevation));
         }
     }
@@ -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();
@@ -252,14 +260,14 @@
             setColorScale(color, m2);
             m1.postConcat(m2);
 
-            if (Utilities.isLmpOrAbove()) {
+            if (Utilities.ATLEAST_LOLLIPOP) {
                 animateFilterTo(m1.getArray());
             } else {
                 mPaint.setColorFilter(new ColorMatrixColorFilter(m1));
                 invalidate();
             }
         } else {
-            if (!Utilities.isLmpOrAbove() || mCurrentFilter == null) {
+            if (!Utilities.ATLEAST_LOLLIPOP || mCurrentFilter == null) {
                 mPaint.setColorFilter(null);
                 invalidate();
             } else {
@@ -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/AbstractUserComparator.java b/src/com/android/launcher3/model/AbstractUserComparator.java
index cf47ce6..bd28560 100644
--- a/src/com/android/launcher3/model/AbstractUserComparator.java
+++ b/src/com/android/launcher3/model/AbstractUserComparator.java
@@ -22,14 +22,12 @@
 import com.android.launcher3.compat.UserManagerCompat;
 
 import java.util.Comparator;
-import java.util.HashMap;
 
 /**
  * A comparator to arrange items based on user profiles.
  */
 public abstract class AbstractUserComparator<T extends ItemInfo> implements Comparator<T> {
 
-    private HashMap<UserHandleCompat, Long> mUserSerialCache = new HashMap<>();
     private final UserManagerCompat mUserManager;
     private final UserHandleCompat mMyUser;
 
@@ -43,25 +41,9 @@
         if (mMyUser.equals(lhs.user)) {
             return -1;
         } else {
-            Long aUserSerial = getAndCacheUserSerial(lhs.user);
-            Long bUserSerial = getAndCacheUserSerial(rhs.user);
+            Long aUserSerial = mUserManager.getSerialNumberForUser(lhs.user);
+            Long bUserSerial = mUserManager.getSerialNumberForUser(rhs.user);
             return aUserSerial.compareTo(bUserSerial);
         }
     }
-
-    /**
-     * Returns the user serial for this user, using a cached serial if possible.
-     */
-    private Long getAndCacheUserSerial(UserHandleCompat user) {
-        Long userSerial = mUserSerialCache.get(user);
-        if (userSerial == null) {
-            userSerial = mUserManager.getSerialNumberForUser(user);
-            mUserSerialCache.put(user, userSerial);
-        }
-        return userSerial;
-    }
-
-    public void clearUserCache() {
-        mUserSerialCache.clear();
-    }
 }
diff --git a/src/com/android/launcher3/model/AppNameComparator.java b/src/com/android/launcher3/model/AppNameComparator.java
index c4b74d4..5f80037 100644
--- a/src/com/android/launcher3/model/AppNameComparator.java
+++ b/src/com/android/launcher3/model/AppNameComparator.java
@@ -68,8 +68,6 @@
      * Returns a locale-aware comparator that will alphabetically order a list of applications.
      */
     public Comparator<ItemInfo> getAppInfoComparator() {
-        // Clear the user serial cache so that we get serials as needed in the comparator
-        mAppInfoComparator.clearUserCache();
         return mAppInfoComparator;
     }
 
diff --git a/src/com/android/launcher3/model/MigrateFromRestoreTask.java b/src/com/android/launcher3/model/MigrateFromRestoreTask.java
new file mode 100644
index 0000000..6a529f6
--- /dev/null
+++ b/src/com/android/launcher3/model/MigrateFromRestoreTask.java
@@ -0,0 +1,767 @@
+package com.android.launcher3.model;
+
+import android.content.ComponentName;
+import android.content.ContentProviderOperation;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.database.Cursor;
+import android.graphics.Point;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherProvider;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.PackageInstallerCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.LongArrayMap;
+import com.android.launcher3.util.Thunk;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+
+/**
+ * This class takes care of shrinking the workspace (by maximum of one row and one column), as a
+ * result of restoring from a larger device.
+ */
+public class MigrateFromRestoreTask {
+
+    public static boolean ENABLED = false;
+
+    private static final String TAG = "MigrateFromRestoreTask";
+    private static final boolean DEBUG = true;
+
+    private static final String KEY_MIGRATION_SOURCE_SIZE = "migration_restore_src_size";
+    private static final String KEY_MIGRATION_WIDGET_MINSIZE = "migration_widget_min_size";
+
+    // These are carefully selected weights for various item types (Math.random?), to allow for
+    // the lease absurd migration experience.
+    private static final float WT_SHORTCUT = 1;
+    private static final float WT_APPLICATION = 0.8f;
+    private static final float WT_WIDGET_MIN = 2;
+    private static final float WT_WIDGET_FACTOR = 0.6f;
+    private static final float WT_FOLDER_FACTOR = 0.5f;
+
+    private final Context mContext;
+    private final ContentValues mTempValues = new ContentValues();
+    private final HashMap<String, Point> mWidgetMinSize;
+    private final InvariantDeviceProfile mIdp;
+
+    private HashSet<String> mValidPackages;
+    public ArrayList<Long> mEntryToRemove;
+    private ArrayList<ContentProviderOperation> mUpdateOperations;
+
+    private ArrayList<DbEntry> mCarryOver;
+
+    private final int mSrcX, mSrcY;
+    @Thunk final int mTrgX, mTrgY;
+    private final boolean mShouldRemoveX, mShouldRemoveY;
+
+    public MigrateFromRestoreTask(Context context) {
+        mContext = context;
+
+        SharedPreferences prefs = prefs(context);
+        Point sourceSize = parsePoint(prefs.getString(KEY_MIGRATION_SOURCE_SIZE, ""));
+        mSrcX = sourceSize.x;
+        mSrcY = sourceSize.y;
+
+        mWidgetMinSize = new HashMap<String, Point>();
+        for (String s : prefs.getStringSet(KEY_MIGRATION_WIDGET_MINSIZE,
+                Collections.<String>emptySet())) {
+            String[] parts = s.split("#");
+            mWidgetMinSize.put(parts[0], parsePoint(parts[1]));
+        }
+
+        mIdp = LauncherAppState.getInstance().getInvariantDeviceProfile();
+        mTrgX = mIdp.numColumns;
+        mTrgY = mIdp.numRows;
+        mShouldRemoveX = mTrgX < mSrcX;
+        mShouldRemoveY = mTrgY < mSrcY;
+    }
+
+    public void execute() throws Exception {
+        mEntryToRemove = new ArrayList<>();
+        mCarryOver = new ArrayList<>();
+        mUpdateOperations = new ArrayList<>();
+
+        // Initialize list of valid packages. This contain all the packages which are already on
+        // the device and packages which are being installed. Any item which doesn't belong to
+        // this set is removed.
+        // Since the loader removes such items anyway, removing these items here doesn't cause any
+        // extra data loss and gives us more free space on the grid for better migration.
+        mValidPackages = new HashSet<>();
+        for (PackageInfo info : mContext.getPackageManager().getInstalledPackages(0)) {
+            mValidPackages.add(info.packageName);
+        }
+        mValidPackages.addAll(PackageInstallerCompat.getInstance(mContext)
+                .updateAndGetActiveSessionCache().keySet());
+
+        ArrayList<Long> allScreens = LauncherModel.loadWorkspaceScreensDb(mContext);
+        if (allScreens.isEmpty()) {
+            throw new Exception("Unable to get workspace screens");
+        }
+
+        for (long screenId : allScreens) {
+            if (DEBUG) {
+                Log.d(TAG, "Migrating " + screenId);
+            }
+            migrateScreen(screenId);
+        }
+
+        if (!mCarryOver.isEmpty()) {
+            LongArrayMap<DbEntry> itemMap = new LongArrayMap<>();
+            for (DbEntry e : mCarryOver) {
+                itemMap.put(e.id, e);
+            }
+
+            do {
+                // Some items are still remaining. Try adding a few new screens.
+
+                // At every iteration, make sure that at least one item is removed from
+                // {@link #mCarryOver}, to prevent an infinite loop. If no item could be removed,
+                // break the loop and abort migration by throwing an exception.
+                OptimalPlacementSolution placement = new OptimalPlacementSolution(
+                        new boolean[mTrgX][mTrgY], deepCopy(mCarryOver), true);
+                placement.find();
+                if (placement.finalPlacedItems.size() > 0) {
+                    long newScreenId = LauncherAppState.getLauncherProvider().generateNewScreenId();
+                    allScreens.add(newScreenId);
+                    for (DbEntry item : placement.finalPlacedItems) {
+                        if (!mCarryOver.remove(itemMap.get(item.id))) {
+                            throw new Exception("Unable to find matching items");
+                        }
+                        item.screenId = newScreenId;
+                        update(item);
+                    }
+                } else {
+                    throw new Exception("None of the items can be placed on an empty screen");
+                }
+
+            } while (!mCarryOver.isEmpty());
+
+
+            LauncherAppState.getInstance().getModel()
+                .updateWorkspaceScreenOrder(mContext, allScreens);
+        }
+
+        // Update items
+        mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY, mUpdateOperations);
+
+        if (!mEntryToRemove.isEmpty()) {
+            if (DEBUG) {
+                Log.d(TAG, "Removing items: " + TextUtils.join(", ", mEntryToRemove));
+            }
+            mContext.getContentResolver().delete(LauncherSettings.Favorites.CONTENT_URI,
+                    Utilities.createDbSelectionQuery(
+                            LauncherSettings.Favorites._ID, mEntryToRemove), null);
+        }
+
+        // Make sure we haven't removed everything.
+        final Cursor c = mContext.getContentResolver().query(
+                LauncherSettings.Favorites.CONTENT_URI, null, null, null, null);
+        boolean hasData = c.moveToNext();
+        c.close();
+        if (!hasData) {
+            throw new Exception("Removed every thing during grid resize");
+        }
+    }
+
+    /**
+     * Migrate a particular screen id.
+     * Strategy:
+     *   1) For all possible combinations of row and column, pick the one which causes the least
+     *      data loss: {@link #tryRemove(int, int, ArrayList, float[])}
+     *   2) Maintain a list of all lost items before this screen, and add any new item lost from
+     *      this screen to that list as well.
+     *   3) If all those items from the above list can be placed on this screen, place them
+     *      (otherwise they are placed on a new screen).
+     */
+    private void migrateScreen(long screenId) {
+        ArrayList<DbEntry> items = loadEntries(screenId);
+
+        int removedCol = Integer.MAX_VALUE;
+        int removedRow = Integer.MAX_VALUE;
+
+        // removeWt represents the cost function for loss of items during migration, and moveWt
+        // represents the cost function for repositioning the items. moveWt is only considered if
+        // removeWt is same for two different configurations.
+        // Start with Float.MAX_VALUE (assuming full data) and pick the configuration with least
+        // cost.
+        float removeWt = Float.MAX_VALUE;
+        float moveWt = Float.MAX_VALUE;
+        float[] outLoss = new float[2];
+        ArrayList<DbEntry> finalItems = null;
+
+        // Try removing all possible combinations
+        for (int x = 0; x < mSrcX; x++) {
+            for (int y = 0; y < mSrcY; y++) {
+                // Use a deep copy when trying out a particular combination as it can change
+                // the underlying object.
+                ArrayList<DbEntry> itemsOnScreen = tryRemove(x, y, deepCopy(items), outLoss);
+
+                if ((outLoss[0] < removeWt) || ((outLoss[0] == removeWt) && (outLoss[1] < moveWt))) {
+                    removeWt = outLoss[0];
+                    moveWt = outLoss[1];
+                    removedCol = mShouldRemoveX ? x : removedCol;
+                    removedRow = mShouldRemoveY ? y : removedRow;
+                    finalItems = itemsOnScreen;
+                }
+
+                // No need to loop over all rows, if a row removal is not needed.
+                if (!mShouldRemoveY) {
+                    break;
+                }
+            }
+
+            if (!mShouldRemoveX) {
+                break;
+            }
+        }
+
+        if (DEBUG) {
+            Log.d(TAG, String.format("Removing row %d, column %d on screen %d",
+                    removedRow, removedCol, screenId));
+        }
+
+        LongArrayMap<DbEntry> itemMap = new LongArrayMap<>();
+        for (DbEntry e : deepCopy(items)) {
+            itemMap.put(e.id, e);
+        }
+
+        for (DbEntry item : finalItems) {
+            DbEntry org = itemMap.get(item.id);
+            itemMap.remove(item.id);
+
+            // Check if update is required
+            if (!item.columnsSame(org)) {
+                update(item);
+            }
+        }
+
+        // The remaining items in {@link #itemMap} are those which didn't get placed.
+        for (DbEntry item : itemMap) {
+            mCarryOver.add(item);
+        }
+
+        if (!mCarryOver.isEmpty() && removeWt == 0) {
+            // No new items were removed in this step. Try placing all the items on this screen.
+            boolean[][] occupied = new boolean[mTrgX][mTrgY];
+            for (DbEntry item : finalItems) {
+                markCells(occupied, item, true);
+            }
+
+            OptimalPlacementSolution placement = new OptimalPlacementSolution(occupied,
+                    deepCopy(mCarryOver), true);
+            placement.find();
+            if (placement.lowestWeightLoss == 0) {
+                // All items got placed
+
+                for (DbEntry item : placement.finalPlacedItems) {
+                    item.screenId = screenId;
+                    update(item);
+                }
+
+                mCarryOver.clear();
+            }
+        }
+    }
+
+    /**
+     * Updates an item in the DB.
+     */
+    private void update(DbEntry item) {
+        mTempValues.clear();
+        item.addToContentValues(mTempValues);
+        mUpdateOperations.add(ContentProviderOperation
+                .newUpdate(LauncherSettings.Favorites.getContentUri(item.id))
+                .withValues(mTempValues).build());
+    }
+
+    /**
+     * Tries the remove the provided row and column.
+     * @param items all the items on the screen under operation
+     * @param outLoss array of size 2. The first entry is filled with weight loss, and the second
+     * with the overall item movement.
+     */
+    private ArrayList<DbEntry> tryRemove(int col, int row, ArrayList<DbEntry> items,
+            float[] outLoss) {
+        boolean[][] occupied = new boolean[mTrgX][mTrgY];
+
+        col = mShouldRemoveX ? col : Integer.MAX_VALUE;
+        row = mShouldRemoveY ? row : Integer.MAX_VALUE;
+
+        ArrayList<DbEntry> finalItems = new ArrayList<>();
+        ArrayList<DbEntry> removedItems = new ArrayList<>();
+
+        for (DbEntry item : items) {
+            if ((item.cellX <= col && (item.spanX + item.cellX) > col)
+                || (item.cellY <= row && (item.spanY + item.cellY) > row)) {
+                removedItems.add(item);
+                if (item.cellX >= col) item.cellX --;
+                if (item.cellY >= row) item.cellY --;
+            } else {
+                if (item.cellX > col) item.cellX --;
+                if (item.cellY > row) item.cellY --;
+                finalItems.add(item);
+                markCells(occupied, item, true);
+            }
+        }
+
+        OptimalPlacementSolution placement = new OptimalPlacementSolution(occupied, removedItems);
+        placement.find();
+        finalItems.addAll(placement.finalPlacedItems);
+        outLoss[0] = placement.lowestWeightLoss;
+        outLoss[1] = placement.lowestMoveCost;
+        return finalItems;
+    }
+
+    @Thunk void markCells(boolean[][] occupied, DbEntry item, boolean val) {
+        for (int i = item.cellX; i < (item.cellX + item.spanX); i++) {
+            for (int j = item.cellY; j < (item.cellY + item.spanY); j++) {
+                occupied[i][j] = val;
+            }
+        }
+    }
+
+    @Thunk boolean isVacant(boolean[][] occupied, int x, int y, int w, int h) {
+        if (x + w > mTrgX) return false;
+        if (y + h > mTrgY) return false;
+
+        for (int i = 0; i < w; i++) {
+            for (int j = 0; j < h; j++) {
+                if (occupied[i + x][j + y]) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    private class OptimalPlacementSolution {
+        private final ArrayList<DbEntry> itemsToPlace;
+        private final boolean[][] occupied;
+
+        // If set to true, item movement are not considered in move cost, leading to a more
+        // linear placement.
+        private final boolean ignoreMove;
+
+        float lowestWeightLoss = Float.MAX_VALUE;
+        float lowestMoveCost = Float.MAX_VALUE;
+        ArrayList<DbEntry> finalPlacedItems;
+
+        public OptimalPlacementSolution(boolean[][] occupied, ArrayList<DbEntry> itemsToPlace) {
+            this(occupied, itemsToPlace, false);
+        }
+
+        public OptimalPlacementSolution(boolean[][] occupied, ArrayList<DbEntry> itemsToPlace,
+                boolean ignoreMove) {
+            this.occupied = occupied;
+            this.itemsToPlace = itemsToPlace;
+            this.ignoreMove = ignoreMove;
+
+            // Sort the items such that larger widgets appear first followed by 1x1 items
+            Collections.sort(this.itemsToPlace);
+        }
+
+        public void find() {
+            find(0, 0, 0, new ArrayList<DbEntry>());
+        }
+
+        /**
+         * Recursively finds a placement for the provided items.
+         * @param index the position in {@link #itemsToPlace} to start looking at.
+         * @param weightLoss total weight loss upto this point
+         * @param moveCost total move cost upto this point
+         * @param itemsPlaced all the items already placed upto this point
+         */
+        public void find(int index, float weightLoss, float moveCost,
+                ArrayList<DbEntry> itemsPlaced) {
+            if ((weightLoss >= lowestWeightLoss) ||
+                    ((weightLoss == lowestWeightLoss) && (moveCost >= lowestMoveCost))) {
+                // Abort, as we already have a better solution.
+                return;
+
+            } else if (index >= itemsToPlace.size()) {
+                // End loop.
+                lowestWeightLoss = weightLoss;
+                lowestMoveCost = moveCost;
+
+                // Keep a deep copy of current configuration as it can change during recursion.
+                finalPlacedItems = deepCopy(itemsPlaced);
+                return;
+            }
+
+            DbEntry me = itemsToPlace.get(index);
+            int myX = me.cellX;
+            int myY = me.cellY;
+
+            // List of items to pass over if this item was placed.
+            ArrayList<DbEntry> itemsIncludingMe = new ArrayList<>(itemsPlaced.size() + 1);
+            itemsIncludingMe.addAll(itemsPlaced);
+            itemsIncludingMe.add(me);
+
+            if (me.spanX > 1 || me.spanY > 1) {
+                // If the current item is a widget (and it greater than 1x1), try to place it at
+                // all possible positions. This is because a widget placed at one position can
+                // affect the placement of a different widget.
+                int myW = me.spanX;
+                int myH = me.spanY;
+
+                for (int y = 0; y < mTrgY; y++) {
+                    for (int x = 0; x < mTrgX; x++) {
+                        float newMoveCost = moveCost;
+                        if (x != myX) {
+                            me.cellX = x;
+                            newMoveCost ++;
+                        }
+                        if (y != myY) {
+                            me.cellY = y;
+                            newMoveCost ++;
+                        }
+                        if (ignoreMove) {
+                            newMoveCost = moveCost;
+                        }
+
+                        if (isVacant(occupied, x, y, myW, myH)) {
+                            // place at this position and continue search.
+                            markCells(occupied, me, true);
+                            find(index + 1, weightLoss, newMoveCost, itemsIncludingMe);
+                            markCells(occupied, me, false);
+                        }
+
+                        // Try resizing horizontally
+                        if (myW > me.minSpanX && isVacant(occupied, x, y, myW - 1, myH)) {
+                            me.spanX --;
+                            markCells(occupied, me, true);
+                            // 1 extra move cost
+                            find(index + 1, weightLoss, newMoveCost + 1, itemsIncludingMe);
+                            markCells(occupied, me, false);
+                            me.spanX ++;
+                        }
+
+                        // Try resizing vertically
+                        if (myH > me.minSpanY && isVacant(occupied, x, y, myW, myH - 1)) {
+                            me.spanY --;
+                            markCells(occupied, me, true);
+                            // 1 extra move cost
+                            find(index + 1, weightLoss, newMoveCost + 1, itemsIncludingMe);
+                            markCells(occupied, me, false);
+                            me.spanY ++;
+                        }
+
+                        // Try resizing horizontally & vertically
+                        if (myH > me.minSpanY && myW > me.minSpanX &&
+                                isVacant(occupied, x, y, myW - 1, myH - 1)) {
+                            me.spanX --;
+                            me.spanY --;
+                            markCells(occupied, me, true);
+                            // 2 extra move cost
+                            find(index + 1, weightLoss, newMoveCost + 2, itemsIncludingMe);
+                            markCells(occupied, me, false);
+                            me.spanX ++;
+                            me.spanY ++;
+                        }
+                        me.cellX = myX;
+                        me.cellY = myY;
+                    }
+                }
+
+                // Finally also try a solution when this item is not included. Trying it in the end
+                // causes it to get skipped in most cases due to higher weight loss, and prevents
+                // unnecessary deep copies of various configurations.
+                find(index + 1, weightLoss + me.weight, moveCost, itemsPlaced);
+            } else {
+                // Since this is a 1x1 item and all the following items are also 1x1, just place
+                // it at 'the most appropriate position' and hope for the best.
+                // The most appropriate position: one with lease straight line distance
+                int newDistance = Integer.MAX_VALUE;
+                int newX = Integer.MAX_VALUE, newY = Integer.MAX_VALUE;
+
+                for (int y = 0; y < mTrgY; y++) {
+                    for (int x = 0; x < mTrgX; x++) {
+                        if (!occupied[x][y]) {
+                            int dist = ignoreMove ? 0 :
+                                ((me.cellX - x) * (me.cellX - x) + (me.cellY - y) * (me.cellY - y));
+                            if (dist < newDistance) {
+                                newX = x;
+                                newY = y;
+                                newDistance = dist;
+                            }
+                        }
+                    }
+                }
+
+                if (newX < mTrgX && newY < mTrgY) {
+                    float newMoveCost = moveCost;
+                    if (newX != myX) {
+                        me.cellX = newX;
+                        newMoveCost ++;
+                    }
+                    if (newY != myY) {
+                        me.cellY = newY;
+                        newMoveCost ++;
+                    }
+                    if (ignoreMove) {
+                        newMoveCost = moveCost;
+                    }
+                    markCells(occupied, me, true);
+                    find(index + 1, weightLoss, newMoveCost, itemsIncludingMe);
+                    markCells(occupied, me, false);
+                    me.cellX = myX;
+                    me.cellY = myY;
+
+                    // Try to find a solution without this item, only if
+                    //  1) there was at least one space, i.e., we were able to place this item
+                    //  2) if the next item has the same weight (all items are already sorted), as
+                    //     if it has lower weight, that solution will automatically get discarded.
+                    //  3) ignoreMove false otherwise, move cost is ignored and the weight will
+                    //      anyway be same.
+                    if (index + 1 < itemsToPlace.size()
+                            && itemsToPlace.get(index + 1).weight >= me.weight && !ignoreMove) {
+                        find(index + 1, weightLoss + me.weight, moveCost, itemsPlaced);
+                    }
+                } else {
+                    // No more space. Jump to the end.
+                    for (int i = index + 1; i < itemsToPlace.size(); i++) {
+                        weightLoss += itemsToPlace.get(i).weight;
+                    }
+                    find(itemsToPlace.size(), weightLoss + me.weight, moveCost, itemsPlaced);
+                }
+            }
+        }
+    }
+
+    /**
+     * Loads entries for a particular screen id.
+     */
+    public ArrayList<DbEntry> loadEntries(long screen) {
+       Cursor c =  mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
+                new String[] {
+                    Favorites._ID,                  // 0
+                    Favorites.ITEM_TYPE,            // 1
+                    Favorites.CELLX,                // 2
+                    Favorites.CELLY,                // 3
+                    Favorites.SPANX,                // 4
+                    Favorites.SPANY,                // 5
+                    Favorites.INTENT,               // 6
+                    Favorites.APPWIDGET_PROVIDER},  // 7
+                Favorites.CONTAINER + " = " + Favorites.CONTAINER_DESKTOP
+                    + " AND " + Favorites.SCREEN + " = " + screen, null, null, null);
+
+       final int indexId = c.getColumnIndexOrThrow(Favorites._ID);
+       final int indexItemType = c.getColumnIndexOrThrow(Favorites.ITEM_TYPE);
+       final int indexCellX = c.getColumnIndexOrThrow(Favorites.CELLX);
+       final int indexCellY = c.getColumnIndexOrThrow(Favorites.CELLY);
+       final int indexSpanX = c.getColumnIndexOrThrow(Favorites.SPANX);
+       final int indexSpanY = c.getColumnIndexOrThrow(Favorites.SPANY);
+       final int indexIntent = c.getColumnIndexOrThrow(Favorites.INTENT);
+       final int indexAppWidgetProvider = c.getColumnIndexOrThrow(Favorites.APPWIDGET_PROVIDER);
+
+       ArrayList<DbEntry> entries = new ArrayList<>();
+       while (c.moveToNext()) {
+           DbEntry entry = new DbEntry();
+           entry.id = c.getLong(indexId);
+           entry.itemType = c.getInt(indexItemType);
+           entry.cellX = c.getInt(indexCellX);
+           entry.cellY = c.getInt(indexCellY);
+           entry.spanX = c.getInt(indexSpanX);
+           entry.spanY = c.getInt(indexSpanY);
+           entry.screenId = screen;
+
+           try {
+               // calculate weight
+               switch (entry.itemType) {
+                   case Favorites.ITEM_TYPE_SHORTCUT:
+                   case Favorites.ITEM_TYPE_APPLICATION: {
+                       verifyIntent(c.getString(indexIntent));
+                       entry.weight = entry.itemType == Favorites.ITEM_TYPE_SHORTCUT
+                           ? WT_SHORTCUT : WT_APPLICATION;
+                       break;
+                   }
+                   case Favorites.ITEM_TYPE_APPWIDGET: {
+                       String provider = c.getString(indexAppWidgetProvider);
+                       ComponentName cn = ComponentName.unflattenFromString(provider);
+                       verifyPackage(cn.getPackageName());
+                       entry.weight = Math.max(WT_WIDGET_MIN, WT_WIDGET_FACTOR
+                               * entry.spanX * entry.spanY);
+
+                       // Migration happens for current user only.
+                       LauncherAppWidgetProviderInfo pInfo = LauncherModel.getProviderInfo(
+                               mContext, cn, UserHandleCompat.myUserHandle());
+                       Point spans = pInfo == null ?
+                               mWidgetMinSize.get(provider) : pInfo.getMinSpans(mIdp, mContext);
+                       if (spans != null) {
+                           entry.minSpanX = spans.x > 0 ? spans.x : entry.spanX;
+                           entry.minSpanY = spans.y > 0 ? spans.y : entry.spanY;
+                       } else {
+                           // Assume that the widget be resized down to 2x2
+                           entry.minSpanX = entry.minSpanY = 2;
+                       }
+
+                       if (entry.minSpanX > mTrgX || entry.minSpanY > mTrgY) {
+                           throw new Exception("Widget can't be resized down to fit the grid");
+                       }
+                       break;
+                   }
+                   case Favorites.ITEM_TYPE_FOLDER: {
+                       int total = getFolderItemsCount(entry.id);
+                       if (total == 0) {
+                           throw new Exception("Folder is empty");
+                       }
+                       entry.weight = WT_FOLDER_FACTOR * total;
+                       break;
+                   }
+                   default:
+                       throw new Exception("Invalid item type");
+               }
+           } catch (Exception e) {
+               if (DEBUG) {
+                   Log.d(TAG, "Removing item " + entry.id, e);
+               }
+               mEntryToRemove.add(entry.id);
+               continue;
+           }
+
+           entries.add(entry);
+       }
+       return entries;
+    }
+
+    /**
+     * @return the number of valid items in the folder.
+     */
+    private int getFolderItemsCount(long folderId) {
+        Cursor c =  mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
+                new String[] {Favorites._ID, Favorites.INTENT},
+                Favorites.CONTAINER + " = " + folderId, null, null, null);
+
+        int total = 0;
+        while (c.moveToNext()) {
+            try {
+                verifyIntent(c.getString(1));
+                total++;
+            } catch (Exception e) {
+                mEntryToRemove.add(c.getLong(0));
+            }
+        }
+
+        return total;
+    }
+
+    /**
+     * Verifies if the intent should be restored.
+     */
+    private void verifyIntent(String intentStr) throws Exception {
+        Intent intent = Intent.parseUri(intentStr, 0);
+        if (intent.getComponent() != null) {
+            verifyPackage(intent.getComponent().getPackageName());
+        } else if (intent.getPackage() != null) {
+            // Only verify package if the component was null.
+            verifyPackage(intent.getPackage());
+        }
+    }
+
+    /**
+     * Verifies if the package should be restored
+     */
+    private void verifyPackage(String packageName) throws Exception {
+        if (!mValidPackages.contains(packageName)) {
+            throw new Exception("Package not available");
+        }
+    }
+
+    private static class DbEntry extends ItemInfo implements Comparable<DbEntry> {
+
+        public float weight;
+
+        public DbEntry() { }
+
+        public DbEntry copy() {
+            DbEntry entry = new DbEntry();
+            entry.copyFrom(this);
+            entry.weight = weight;
+            entry.minSpanX = minSpanX;
+            entry.minSpanY = minSpanY;
+            return entry;
+        }
+
+        /**
+         * Comparator such that larger widgets come first,  followed by all 1x1 items
+         * based on their weights.
+         */
+        @Override
+        public int compareTo(DbEntry another) {
+            if (itemType == Favorites.ITEM_TYPE_APPWIDGET) {
+                if (another.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
+                    return another.spanY * another.spanX - spanX * spanY;
+                } else {
+                    return -1;
+                }
+            } else if (another.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
+                return 1;
+            } else {
+                // Place higher weight before lower weight.
+                return Float.compare(another.weight, weight);
+            }
+        }
+
+        public boolean columnsSame(DbEntry org) {
+            return org.cellX == cellX && org.cellY == cellY && org.spanX == spanX &&
+                    org.spanY == spanY && org.screenId == screenId;
+        }
+
+        public void addToContentValues(ContentValues values) {
+            values.put(LauncherSettings.Favorites.SCREEN, screenId);
+            values.put(LauncherSettings.Favorites.CELLX, cellX);
+            values.put(LauncherSettings.Favorites.CELLY, cellY);
+            values.put(LauncherSettings.Favorites.SPANX, spanX);
+            values.put(LauncherSettings.Favorites.SPANY, spanY);
+        }
+    }
+
+    @Thunk static ArrayList<DbEntry> deepCopy(ArrayList<DbEntry> src) {
+        ArrayList<DbEntry> dup = new ArrayList<DbEntry>(src.size());
+        for (DbEntry e : src) {
+            dup.add(e.copy());
+        }
+        return dup;
+    }
+
+    private static Point parsePoint(String point) {
+        String[] split = point.split(",");
+        return new Point(Integer.parseInt(split[0]), Integer.parseInt(split[1]));
+    }
+
+    public static void markForMigration(Context context, int srcX, int srcY,
+            HashSet<String> widgets) {
+        prefs(context).edit()
+                .putString(KEY_MIGRATION_SOURCE_SIZE, srcX + "," + srcY)
+                .putStringSet(KEY_MIGRATION_WIDGET_MINSIZE, widgets)
+                .apply();
+    }
+
+    public static boolean shouldRunTask(Context context) {
+        return !TextUtils.isEmpty(prefs(context).getString(KEY_MIGRATION_SOURCE_SIZE, ""));
+    }
+
+    public static void clearFlags(Context context) {
+        prefs(context).edit().remove(KEY_MIGRATION_SOURCE_SIZE)
+                .remove(KEY_MIGRATION_WIDGET_MINSIZE).commit();
+    }
+
+    private static SharedPreferences prefs(Context context) {
+        return context.getSharedPreferences(LauncherAppState.getSharedPreferencesKey(),
+                Context.MODE_PRIVATE);
+    }
+}
diff --git a/src/com/android/launcher3/model/WidgetsAndShortcutNameComparator.java b/src/com/android/launcher3/model/WidgetsAndShortcutNameComparator.java
index 61e8952..b990560 100644
--- a/src/com/android/launcher3/model/WidgetsAndShortcutNameComparator.java
+++ b/src/com/android/launcher3/model/WidgetsAndShortcutNameComparator.java
@@ -1,13 +1,14 @@
 package com.android.launcher3.model;
 
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.ComponentKey;
 
 import java.text.Collator;
 import java.util.Comparator;
@@ -16,53 +17,81 @@
 public class WidgetsAndShortcutNameComparator implements Comparator<Object> {
     private final AppWidgetManagerCompat mManager;
     private final PackageManager mPackageManager;
-    private final HashMap<Object, String> mLabelCache;
+    private final HashMap<ComponentKey, String> mLabelCache;
     private final Collator mCollator;
     private final UserHandleCompat mMainHandle;
 
     public WidgetsAndShortcutNameComparator(Context context) {
         mManager = AppWidgetManagerCompat.getInstance(context);
         mPackageManager = context.getPackageManager();
-        mLabelCache = new HashMap<Object, String>();
+        mLabelCache = new HashMap<>();
         mCollator = Collator.getInstance();
         mMainHandle = UserHandleCompat.myUserHandle();
     }
 
+    /**
+     * Resets any stored state.
+     */
+    public void reset() {
+        mLabelCache.clear();
+    }
+
     @Override
-    public final int compare(Object a, Object b) {
-        String labelA, labelB;
-        if (mLabelCache.containsKey(a)) {
-            labelA = mLabelCache.get(a);
-        } else {
-            labelA = (a instanceof LauncherAppWidgetProviderInfo)
-                    ? Utilities.trim(mManager.loadLabel((LauncherAppWidgetProviderInfo) a))
-                    : Utilities.trim(((ResolveInfo) a).loadLabel(mPackageManager));
-            mLabelCache.put(a, labelA);
-        }
-        if (mLabelCache.containsKey(b)) {
-            labelB = mLabelCache.get(b);
-        } else {
-            labelB = (b instanceof LauncherAppWidgetProviderInfo)
-                    ? Utilities.trim(mManager.loadLabel((LauncherAppWidgetProviderInfo) b))
-                    : Utilities.trim(((ResolveInfo) b).loadLabel(mPackageManager));
-            mLabelCache.put(b, labelB);
-        }
-
-        // Currently, there is no work profile shortcuts, hence only considering the widget cases.
-
-        boolean aWorkProfile = (a instanceof LauncherAppWidgetProviderInfo) &&
-                !mMainHandle.equals(mManager.getUser((LauncherAppWidgetProviderInfo) a));
-        boolean bWorkProfile = (b instanceof LauncherAppWidgetProviderInfo) &&
-                !mMainHandle.equals(mManager.getUser((LauncherAppWidgetProviderInfo) b));
+    public final int compare(Object objA, Object objB) {
+        ComponentKey keyA = getComponentKey(objA);
+        ComponentKey keyB = getComponentKey(objB);
 
         // Independent of how the labels compare, if only one of the two widget info belongs to
         // work profile, put that one in the back.
+        boolean aWorkProfile = !mMainHandle.equals(keyA.user);
+        boolean bWorkProfile = !mMainHandle.equals(keyB.user);
         if (aWorkProfile && !bWorkProfile) {
             return 1;
         }
         if (!aWorkProfile && bWorkProfile) {
             return -1;
         }
+
+        // Get the labels for comparison
+        String labelA = mLabelCache.get(keyA);
+        String labelB = mLabelCache.get(keyB);
+        if (labelA == null) {
+            labelA = getLabel(objA);
+            mLabelCache.put(keyA, labelA);
+        }
+        if (labelB == null) {
+            labelB = getLabel(objB);
+            mLabelCache.put(keyB, labelB);
+        }
         return mCollator.compare(labelA, labelB);
     }
+
+    /**
+     * @return a component key for the given widget or shortcut info.
+     */
+    private ComponentKey getComponentKey(Object o) {
+        if (o instanceof LauncherAppWidgetProviderInfo) {
+            LauncherAppWidgetProviderInfo widgetInfo = (LauncherAppWidgetProviderInfo) o;
+            return new ComponentKey(widgetInfo.provider, mManager.getUser(widgetInfo));
+        } else {
+            ResolveInfo shortcutInfo = (ResolveInfo) o;
+            ComponentName cn = new ComponentName(shortcutInfo.activityInfo.packageName,
+                    shortcutInfo.activityInfo.name);
+            // Currently, there are no work profile shortcuts
+            return new ComponentKey(cn, UserHandleCompat.myUserHandle());
+        }
+    }
+
+    /**
+     * @return the label for the given widget or shortcut info.  This may be an expensive call.
+     */
+    private String getLabel(Object o) {
+        if (o instanceof LauncherAppWidgetProviderInfo) {
+            LauncherAppWidgetProviderInfo widgetInfo = (LauncherAppWidgetProviderInfo) o;
+            return Utilities.trim(mManager.loadLabel(widgetInfo));
+        } else {
+            ResolveInfo shortcutInfo = (ResolveInfo) o;
+            return Utilities.trim(shortcutInfo.loadLabel(mPackageManager));
+        }
+    }
 };
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index 185dfca..eef4f91 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -8,6 +8,9 @@
 
 import com.android.launcher3.AppFilter;
 import com.android.launcher3.IconCache;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.AlphabeticIndexCompat;
@@ -39,8 +42,8 @@
     private ArrayList<Object> mRawList;
 
     private final AppWidgetManagerCompat mAppWidgetMgr;
-    private final Comparator mWidgetAndShortcutNameComparator;
-    private final Comparator mAppNameComparator;
+    private final WidgetsAndShortcutNameComparator mWidgetAndShortcutNameComparator;
+    private final Comparator<ItemInfo> mAppNameComparator;
     private final IconCache mIconCache;
     private final AppFilter mAppFilter;
     private AlphabeticIndexCompat mIndexer;
@@ -54,6 +57,7 @@
         mIndexer = new AlphabeticIndexCompat(context);
     }
 
+    @SuppressWarnings("unchecked")
     private WidgetsModel(WidgetsModel model) {
         mAppWidgetMgr = model.mAppWidgetMgr;
         mPackageItemInfos = (ArrayList<PackageItemInfo>) model.mPackageItemInfos.clone();
@@ -103,6 +107,9 @@
         // clear the lists.
         mWidgetsList.clear();
         mPackageItemInfos.clear();
+        mWidgetAndShortcutNameComparator.reset();
+
+        InvariantDeviceProfile idp = LauncherAppState.getInstance().getInvariantDeviceProfile();
 
         // add and update.
         for (Object o: rawWidgetsShortcuts) {
@@ -111,9 +118,23 @@
             ComponentName componentName = null;
             if (o instanceof LauncherAppWidgetProviderInfo) {
                 LauncherAppWidgetProviderInfo widgetInfo = (LauncherAppWidgetProviderInfo) o;
-                componentName = widgetInfo.provider;
-                packageName = widgetInfo.provider.getPackageName();
-                userHandle = mAppWidgetMgr.getUser(widgetInfo);
+
+                // Ensure that all widgets we show can be added on a workspace of this size
+                int minSpanX = Math.min(widgetInfo.spanX, widgetInfo.minSpanX);
+                int minSpanY = Math.min(widgetInfo.spanY, widgetInfo.minSpanY);
+                if (minSpanX <= (int) idp.numColumns &&
+                    minSpanY <= (int) idp.numRows) {
+                    componentName = widgetInfo.provider;
+                    packageName = widgetInfo.provider.getPackageName();
+                    userHandle = mAppWidgetMgr.getUser(widgetInfo);
+                } else {
+                    if (DEBUG) {
+                        Log.d(TAG, String.format(
+                                "Widget %s : (%d X %d) can't fit on this device",
+                                widgetInfo.provider, minSpanX, minSpanY));
+                    }
+                    continue;
+                }
             } else if (o instanceof ResolveInfo) {
                 ResolveInfo resolveInfo = (ResolveInfo) o;
                 componentName = new ComponentName(resolveInfo.activityInfo.packageName,
@@ -139,7 +160,7 @@
             if (widgetsShortcutsList != null) {
                 widgetsShortcutsList.add(o);
             } else {
-                widgetsShortcutsList = new ArrayList<Object>();
+                widgetsShortcutsList = new ArrayList<>();
                 widgetsShortcutsList.add(o);
                 pInfo = new PackageItemInfo(packageName);
                 mIconCache.getTitleAndIconForApp(packageName, userHandle,
diff --git a/src/com/android/launcher3/testing/LauncherExtension.java b/src/com/android/launcher3/testing/LauncherExtension.java
index 34492e4..b4d7459 100644
--- a/src/com/android/launcher3/testing/LauncherExtension.java
+++ b/src/com/android/launcher3/testing/LauncherExtension.java
@@ -202,6 +202,11 @@
         }
 
         @Override
+        public boolean startSearchFromAllApps(String query) {
+            return false;
+        }
+
+        @Override
         public void startVoice() {
         }
 
@@ -330,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/ComponentKey.java b/src/com/android/launcher3/util/ComponentKey.java
index 6a7df43..b7aafae 100644
--- a/src/com/android/launcher3/util/ComponentKey.java
+++ b/src/com/android/launcher3/util/ComponentKey.java
@@ -64,8 +64,11 @@
      * Encodes a component key as a string of the form [flattenedComponentString#userId].
      */
     public String flattenToString(Context context) {
-        return componentName.flattenToString() + "#" +
-                UserManagerCompat.getInstance(context).getSerialNumberForUser(user);
+        String flattened = componentName.flattenToString();
+        if (user != null) {
+            flattened += "#" + UserManagerCompat.getInstance(context).getSerialNumberForUser(user);
+        }
+        return flattened;
     }
 
     @Override
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/ManagedProfileHeuristic.java b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
index b37f447..74fc92a 100644
--- a/src/com/android/launcher3/util/ManagedProfileHeuristic.java
+++ b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
@@ -68,7 +68,7 @@
     private static final long AUTO_ADD_TO_FOLDER_DURATION = 8 * 60 * 60 * 1000;
 
     public static ManagedProfileHeuristic get(Context context, UserHandleCompat user) {
-        if (Utilities.isLmpOrAbove() && !UserHandleCompat.myUserHandle().equals(user)) {
+        if (Utilities.ATLEAST_LOLLIPOP && !UserHandleCompat.myUserHandle().equals(user)) {
             return new ManagedProfileHeuristic(context, user);
         }
         return null;
@@ -296,7 +296,7 @@
      * Verifies that entries corresponding to {@param users} exist and removes all invalid entries.
      */
     public static void processAllUsers(List<UserHandleCompat> users, Context context) {
-        if (!Utilities.isLmpOrAbove()) {
+        if (!Utilities.ATLEAST_LOLLIPOP) {
             return;
         }
         UserManagerCompat userManager = UserManagerCompat.getInstance(context);
diff --git a/src/com/android/launcher3/util/UiThreadCircularReveal.java b/src/com/android/launcher3/util/UiThreadCircularReveal.java
index 3ca3aee..f2b5e5e 100644
--- a/src/com/android/launcher3/util/UiThreadCircularReveal.java
+++ b/src/com/android/launcher3/util/UiThreadCircularReveal.java
@@ -47,7 +47,7 @@
                 float progress = arg0.getAnimatedFraction();
                 outlineProvider.setProgress(progress);
                 revealView.invalidateOutline();
-                if (!Utilities.isLmpMR1OrAbove()) {
+                if (!Utilities.ATLEAST_LOLLIPOP_MR1) {
                     revealView.invalidate();
                 }
             }
diff --git a/src/com/android/launcher3/util/WallpaperUtils.java b/src/com/android/launcher3/util/WallpaperUtils.java
index 53b2acd..a5251e1 100644
--- a/src/com/android/launcher3/util/WallpaperUtils.java
+++ b/src/com/android/launcher3/util/WallpaperUtils.java
@@ -17,13 +17,18 @@
 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;
+
 /**
  * Utility methods for wallpaper management.
  */
@@ -33,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);
         }
     }
 
@@ -62,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
@@ -92,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 (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_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/PendingAddWidgetInfo.java b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
index 758287a..fcb714f 100644
--- a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
+++ b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
@@ -31,10 +31,6 @@
  * @see {@link PendingAddItemInfo}
  */
 public class PendingAddWidgetInfo extends PendingAddItemInfo {
-    public int minWidth;
-    public int minHeight;
-    public int minResizeWidth;
-    public int minResizeHeight;
     public int previewImage;
     public int icon;
     public LauncherAppWidgetProviderInfo info;
@@ -50,17 +46,13 @@
         this.info = i;
         user = AppWidgetManagerCompat.getInstance(launcher).getUser(i);
         componentName = i.provider;
-        minWidth = i.minWidth;
-        minHeight = i.minHeight;
-        minResizeWidth = i.minResizeWidth;
-        minResizeHeight = i.minResizeHeight;
         previewImage = i.previewImage;
         icon = i.icon;
 
-        spanX = i.getSpanX(launcher);
-        spanY = i.getSpanY(launcher);
-        minSpanX = i.getMinSpanX(launcher);
-        minSpanY = i.getMinSpanY(launcher);
+        spanX = i.spanX;
+        spanY = i.spanY;
+        minSpanX = i.minSpanX;
+        minSpanY = i.minSpanY;
     }
 
     public boolean isCustomWidget() {
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 7496ea2..94bbd92 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -146,8 +146,8 @@
         mInfo = info;
         // TODO(hyunyoungs): setup a cache for these labels.
         mWidgetName.setText(AppWidgetManagerCompat.getInstance(getContext()).loadLabel(info));
-        int hSpan = Math.min(info.getSpanX(mLauncher), profile.numColumns);
-        int vSpan = Math.min(info.getSpanY(mLauncher), profile.numRows);
+        int hSpan = Math.min(info.spanX, profile.numColumns);
+        int vSpan = Math.min(info.spanY, profile.numRows);
         mWidgetDims.setText(String.format(mDimensionsFormatString, hSpan, vSpan));
         mWidgetPreviewLoader = loader;
     }
diff --git a/src/com/android/launcher3/widget/WidgetHostViewLoader.java b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
index bdd117c..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 {
@@ -132,7 +133,7 @@
     public static Bundle getDefaultOptionsForWidget(Launcher launcher, PendingAddWidgetInfo info) {
         Bundle options = null;
         Rect rect = new Rect();
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+        if (Utilities.ATLEAST_JB_MR1) {
             AppWidgetResizeFrame.getWidgetSizeRanges(launcher, info.spanX, info.spanY, rect);
             Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(launcher,
                     info.componentName, null);
diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java
index 0f43a34..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");
@@ -319,7 +319,6 @@
                 CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
                 ItemInfo itemInfo = d.dragInfo;
                 if (layout != null) {
-                    layout.calculateSpans(itemInfo);
                     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 a54626a..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) {
@@ -169,7 +165,7 @@
 
         // if the end padding is 0, then container view (horizontal scroll view) doesn't respect
         // the end of the linear layout width + the start padding and doesn't allow scrolling.
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+        if (Utilities.ATLEAST_JB_MR1) {
             cellList.setPaddingRelative(mIndent, 0, 1, 0);
         } else {
             cellList.setPadding(mIndent, 0, 1, 0);
@@ -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/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
index 61e63cd..e586dc2 100644
--- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
@@ -102,7 +102,7 @@
 
         getCurScrollState(mScrollPosState);
         float pos = rowCount * touchFraction;
-        int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight, 0);
+        int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight);
         LinearLayoutManager layoutManager = ((LinearLayoutManager) getLayoutManager());
         layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction));
 
@@ -115,29 +115,29 @@
      * Updates the bounds for the scrollbar.
      */
     @Override
-    public void onUpdateScrollbar() {
+    public void onUpdateScrollbar(int dy) {
         int rowCount = mWidgets.getPackageSize();
 
         // Skip early if, there are no items.
         if (rowCount == 0) {
-            mScrollbar.setScrollbarThumbOffset(-1, -1);
+            mScrollbar.setThumbOffset(-1, -1);
             return;
         }
 
         // Skip early if, there no child laid out in the container.
         getCurScrollState(mScrollPosState);
         if (mScrollPosState.rowIndex < 0) {
-            mScrollbar.setScrollbarThumbOffset(-1, -1);
+            mScrollbar.setThumbOffset(-1, -1);
             return;
         }
 
-        synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount, 0);
+        synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount);
     }
 
     /**
      * Returns the current scroll state.
      */
-    private void getCurScrollState(ScrollPositionState stateOut) {
+    protected void getCurScrollState(ScrollPositionState stateOut) {
         stateOut.rowIndex = -1;
         stateOut.rowTopOffset = -1;
         stateOut.rowHeight = -1;
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 {