Merge "Enabling translation for accessibility strings" into ub-launcher3-burnaby
diff --git a/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java b/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
index affad0f..ee0d8b0 100644
--- a/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
+++ b/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
@@ -131,7 +131,7 @@
 
         // Load image in background
         final BitmapRegionTileSource.UriBitmapSource bitmapSource =
-                new BitmapRegionTileSource.UriBitmapSource(getContext(), imageUri, 1024);
+                new BitmapRegionTileSource.UriBitmapSource(getContext(), imageUri);
         mSetWallpaperButton.setEnabled(false);
         Runnable onLoad = new Runnable() {
             public void run() {
diff --git a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
index 1ba5b4b..72cb194 100644
--- a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
+++ b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
@@ -21,7 +21,6 @@
 import android.app.ActionBar;
 import android.app.Activity;
 import android.app.WallpaperManager;
-import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -144,8 +143,7 @@
         public void onClick(final WallpaperPickerActivity a) {
             a.setWallpaperButtonEnabled(false);
             final BitmapRegionTileSource.UriBitmapSource bitmapSource =
-                    new BitmapRegionTileSource.UriBitmapSource(
-                            a.getContext(), mUri, BitmapRegionTileSource.MAX_PREVIEW_SIZE);
+                    new BitmapRegionTileSource.UriBitmapSource(a.getContext(), mUri);
             a.setCropViewTileSource(bitmapSource, true, false, null, new Runnable() {
 
                 @Override
@@ -199,8 +197,7 @@
         public void onClick(final WallpaperPickerActivity a) {
             a.setWallpaperButtonEnabled(false);
             BitmapRegionTileSource.UriBitmapSource bitmapSource =
-                    new BitmapRegionTileSource.UriBitmapSource(a.getContext(),
-                            Uri.fromFile(mFile), 1024);
+                    new BitmapRegionTileSource.UriBitmapSource(a.getContext(), Uri.fromFile(mFile));
             a.setCropViewTileSource(bitmapSource, false, true, null, new Runnable() {
 
                 @Override
@@ -236,8 +233,7 @@
         public void onClick(final WallpaperPickerActivity a) {
             a.setWallpaperButtonEnabled(false);
             BitmapRegionTileSource.ResourceBitmapSource bitmapSource =
-                    new BitmapRegionTileSource.ResourceBitmapSource(
-                            mResources, mResId, BitmapRegionTileSource.MAX_PREVIEW_SIZE);
+                    new BitmapRegionTileSource.ResourceBitmapSource(mResources, mResId);
             a.setCropViewTileSource(bitmapSource, false, false, new CropViewScaleProvider() {
 
                 @Override
diff --git a/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java b/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java
index 15f97e5..2d496a5 100644
--- a/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java
+++ b/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java
@@ -26,6 +26,7 @@
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.net.Uri;
+import android.opengl.GLUtils;
 import android.os.Build;
 import android.util.Log;
 
@@ -149,18 +150,15 @@
     private static final int GL_SIZE_LIMIT = 2048;
     // This must be no larger than half the size of the GL_SIZE_LIMIT
     // due to decodePreview being allowed to be up to 2x the size of the target
-    public static final int MAX_PREVIEW_SIZE = GL_SIZE_LIMIT / 2;
+    private static final int MAX_PREVIEW_SIZE = GL_SIZE_LIMIT / 2;
 
     public static abstract class BitmapSource {
         private SimpleBitmapRegionDecoder mDecoder;
         private Bitmap mPreview;
-        private final int mPreviewSize;
         private int mRotation;
         public enum State { NOT_LOADED, LOADED, ERROR_LOADING };
         private State mState = State.NOT_LOADED;
-        public BitmapSource(int previewSize) {
-            mPreviewSize = Math.min(previewSize, MAX_PREVIEW_SIZE);
-        }
+
         public boolean loadInBackground(InBitmapProvider bitmapProvider) {
             ExifInterface ei = new ExifInterface();
             if (readExif(ei)) {
@@ -176,36 +174,44 @@
             } else {
                 int width = mDecoder.getWidth();
                 int height = mDecoder.getHeight();
-                if (mPreviewSize != 0) {
-                    BitmapFactory.Options opts = new BitmapFactory.Options();
-                    opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
-                    opts.inPreferQualityOverSpeed = true;
 
-                    float scale = (float) mPreviewSize / Math.max(width, height);
-                    opts.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale);
-                    opts.inJustDecodeBounds = false;
-                    opts.inMutable = true;
+                BitmapFactory.Options opts = new BitmapFactory.Options();
+                opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
+                opts.inPreferQualityOverSpeed = true;
 
-                    if (bitmapProvider != null) {
-                        int expectedPixles = (width / opts.inSampleSize) * (height / opts.inSampleSize);
-                        Bitmap reusableBitmap = bitmapProvider.forPixelCount(expectedPixles);
-                        if (reusableBitmap != null) {
-                            // Try loading with reusable bitmap
-                            opts.inBitmap = reusableBitmap;
-                            try {
-                                mPreview = loadPreviewBitmap(opts);
-                            } catch (IllegalArgumentException e) {
-                                Log.d(TAG, "Unable to reusage bitmap", e);
-                                opts.inBitmap = null;
-                                mPreview = null;
-                            }
+                float scale = (float) MAX_PREVIEW_SIZE / Math.max(width, height);
+                opts.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale);
+                opts.inJustDecodeBounds = false;
+                opts.inMutable = true;
+
+                if (bitmapProvider != null) {
+                    int expectedPixles = (width / opts.inSampleSize) * (height / opts.inSampleSize);
+                    Bitmap reusableBitmap = bitmapProvider.forPixelCount(expectedPixles);
+                    if (reusableBitmap != null) {
+                        // Try loading with reusable bitmap
+                        opts.inBitmap = reusableBitmap;
+                        try {
+                            mPreview = loadPreviewBitmap(opts);
+                        } catch (IllegalArgumentException e) {
+                            Log.d(TAG, "Unable to reusage bitmap", e);
+                            opts.inBitmap = null;
+                            mPreview = null;
                         }
                     }
-                    if (mPreview == null) {
-                        mPreview = loadPreviewBitmap(opts);
-                    }
                 }
-                mState = State.LOADED;
+                if (mPreview == null) {
+                    mPreview = loadPreviewBitmap(opts);
+                }
+
+                // Verify that the bitmap can be used on GL surface
+                try {
+                    GLUtils.getInternalFormat(mPreview);
+                    GLUtils.getType(mPreview);
+                    mState = State.LOADED;
+                } catch (IllegalArgumentException e) {
+                    Log.d(TAG, "Image cannot be rendered on a GL surface", e);
+                    mState = State.ERROR_LOADING;
+                }
                 return true;
             }
         }
@@ -237,8 +243,7 @@
 
     public static class FilePathBitmapSource extends BitmapSource {
         private String mPath;
-        public FilePathBitmapSource(String path, int previewSize) {
-            super(previewSize);
+        public FilePathBitmapSource(String path) {
             mPath = path;
         }
         @Override
@@ -272,8 +277,7 @@
     public static class UriBitmapSource extends BitmapSource {
         private Context mContext;
         private Uri mUri;
-        public UriBitmapSource(Context context, Uri uri, int previewSize) {
-            super(previewSize);
+        public UriBitmapSource(Context context, Uri uri) {
             mContext = context;
             mUri = uri;
         }
@@ -337,8 +341,7 @@
     public static class ResourceBitmapSource extends BitmapSource {
         private Resources mRes;
         private int mResId;
-        public ResourceBitmapSource(Resources res, int resId, int previewSize) {
-            super(previewSize);
+        public ResourceBitmapSource(Resources res, int resId) {
             mRes = res;
             mResId = resId;
         }
diff --git a/res/drawable-hdpi/bg_appwidget_error.9.png b/res/drawable-hdpi/bg_appwidget_error.9.png
deleted file mode 100644
index 4da3195..0000000
--- a/res/drawable-hdpi/bg_appwidget_error.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/bg_appwidget_error.9.png b/res/drawable-mdpi/bg_appwidget_error.9.png
deleted file mode 100644
index 493c0d4..0000000
--- a/res/drawable-mdpi/bg_appwidget_error.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/bg_appwidget_error.9.png b/res/drawable-xhdpi/bg_appwidget_error.9.png
deleted file mode 100644
index b792cc8..0000000
--- a/res/drawable-xhdpi/bg_appwidget_error.9.png
+++ /dev/null
Binary files differ
diff --git a/res/layout/apps_grid_icon_view.xml b/res/layout/apps_grid_icon_view.xml
index 67d7d50..7165f38 100644
--- a/res/layout/apps_grid_icon_view.xml
+++ b/res/layout/apps_grid_icon_view.xml
@@ -25,6 +25,5 @@
     android:paddingBottom="@dimen/apps_icon_top_bottom_padding"
     android:focusable="true"
     android:background="@drawable/focusable_view_bg"
-    launcher:deferShadowGeneration="true"
     launcher:iconDisplay="all_apps" />
 
diff --git a/res/layout/apps_list_view.xml b/res/layout/apps_list_view.xml
index 03ba646..6f9be05 100644
--- a/res/layout/apps_list_view.xml
+++ b/res/layout/apps_list_view.xml
@@ -14,7 +14,8 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.launcher3.AppsRecyclerViewContainer
+    xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/apps_list"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
@@ -101,4 +102,4 @@
             android:src="@drawable/ic_search_grey" />
     </FrameLayout>
 
-</FrameLayout>
\ No newline at end of file
+</com.android.launcher3.AppsRecyclerViewContainer>
\ No newline at end of file
diff --git a/res/layout/apps_prediction_bar_icon_view.xml b/res/layout/apps_prediction_bar_icon_view.xml
index 4a6f157..1e75d14 100644
--- a/res/layout/apps_prediction_bar_icon_view.xml
+++ b/res/layout/apps_prediction_bar_icon_view.xml
@@ -24,6 +24,5 @@
     android:layout_weight="1"
     android:focusable="true"
     android:background="@drawable/focusable_view_bg"
-    launcher:deferShadowGeneration="true"
     launcher:iconDisplay="all_apps" />
 
diff --git a/res/layout/appwidget_error.xml b/res/layout/appwidget_error.xml
index 03d4ae4..708ece4 100644
--- a/res/layout/appwidget_error.xml
+++ b/res/layout/appwidget_error.xml
@@ -15,15 +15,12 @@
 -->
 
 <TextView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:paddingTop="10dip"
-    android:paddingBottom="10dip"
-    android:paddingLeft="20dip"
-    android:paddingRight="20dip"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
     android:gravity="center"
-    android:background="@drawable/bg_appwidget_error"
+    android:elevation="2dp"
+    android:background="@drawable/quantum_panel_dark"
     android:textAppearance="?android:attr/textAppearanceMediumInverse"
-    android:textColor="@color/appwidget_error_color"
+    android:textColor="@color/widgets_view_item_text_color"
     android:text="@string/gadget_error_text"
     />
diff --git a/res/layout/widget_cell.xml b/res/layout/widget_cell.xml
index 500cf10..7fefeba 100644
--- a/res/layout/widget_cell.xml
+++ b/res/layout/widget_cell.xml
@@ -39,16 +39,15 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_weight="1"
-            android:gravity="start"
-            android:singleLine="true"
             android:ellipsize="end"
             android:fadingEdge="horizontal"
-            android:textColor="@color/widgets_view_item_text_color"
-            android:textSize="14sp"
-            android:textAlignment="viewStart"
             android:fontFamily="sans-serif-condensed"
+            android:gravity="start"
+            android:shadowColor="#B0000000"
             android:shadowRadius="2.0"
-            android:shadowColor="#B0000000" />
+            android:singleLine="true"
+            android:textColor="@color/widgets_view_item_text_color"
+            android:textSize="14sp" />
 
         <!-- The original dimensions of the widget (can't be the same text as above due to different
              style. -->
@@ -58,22 +57,18 @@
             android:layout_height="wrap_content"
             android:layout_marginStart="5dp"
             android:layout_marginLeft="5dp"
-            android:layout_weight="0"
-            android:gravity="start"
             android:textColor="@color/widgets_view_item_text_color"
             android:textSize="14sp"
-            android:textAlignment="viewStart"
             android:fontFamily="sans-serif-condensed"
             android:shadowRadius="2.0"
             android:shadowColor="#B0000000" />
     </LinearLayout>
 
-    <!-- The image of the widget. -->
+    <!-- The image of the widget. This view does not support padding. Any placement adjustment
+         should be done using margins. -->
     <com.android.launcher3.widget.WidgetImageView
         android:id="@+id/widget_preview"
-        style="@style/WidgetImageView"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:scaleType="matrix" />
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" />
 </com.android.launcher3.widget.WidgetCell>
\ No newline at end of file
diff --git a/res/layout/widgets_view.xml b/res/layout/widgets_view.xml
index b91ca26..1857595 100644
--- a/res/layout/widgets_view.xml
+++ b/res/layout/widgets_view.xml
@@ -40,6 +40,7 @@
         android:layout_height="match_parent"
         android:clipChildren="false"
         android:elevation="15dp"
+        android:visibility="gone"
         android:orientation="vertical" >
 
         <com.android.launcher3.widget.WidgetsContainerRecyclerView
diff --git a/res/values-v17/styles.xml b/res/values-v17/styles.xml
deleted file mode 100644
index 3589e80..0000000
--- a/res/values-v17/styles.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
-    <style name="WidgetImageView">
-        <item name="android:paddingStart">@dimen/widget_preview_horizontal_padding</item>
-    </style>
-</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 0ba55f3..1e89615 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -27,8 +27,6 @@
 
     <color name="focused_background">#80c6c5c5</color>
 
-    <color name="appwidget_error_color">#FCCC</color>
-
     <color name="workspace_icon_text_color">#FFF</color>
 
     <color name="quantum_panel_text_color">#FF666666</color>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index f95debe..1496da9 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -86,9 +86,4 @@
         <item name="ringOutset">4dp</item>
     </style>
 
-    <!-- Overridden in device overlays -->
-    <style name="WidgetImageView">
-        <item name="android:paddingLeft">@dimen/widget_preview_horizontal_padding</item>
-    </style>
-
 </resources>
diff --git a/src/com/android/launcher3/AlphabeticalAppsList.java b/src/com/android/launcher3/AlphabeticalAppsList.java
index eff7b06..82aaeb9 100644
--- a/src/com/android/launcher3/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/AlphabeticalAppsList.java
@@ -7,6 +7,7 @@
 import com.android.launcher3.compat.AlphabeticIndexCompat;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.model.AppNameComparator;
 
 import java.text.Collator;
 import java.util.ArrayList;
@@ -18,96 +19,6 @@
 import java.util.Map;
 import java.util.TreeMap;
 
-
-/**
- * A private class to manage access to an app name comparator.
- */
-class AppNameComparator {
-    private final UserManagerCompat mUserManager;
-    private final Collator mCollator;
-    private final Comparator<AppInfo> mAppInfoComparator;
-    private final Comparator<String> mSectionNameComparator;
-    private HashMap<UserHandleCompat, Long> mUserSerialCache = new HashMap<>();
-
-    public AppNameComparator(Context context) {
-        mCollator = Collator.getInstance();
-        mUserManager = UserManagerCompat.getInstance(context);
-        mAppInfoComparator = new Comparator<AppInfo>() {
-            public final int compare(AppInfo a, AppInfo b) {
-                // Order by the title in the current locale
-                int result = compareTitles(a.title.toString(), b.title.toString());
-                if (result == 0) {
-                    // If two apps have the same title, then order by the component name
-                    result = a.componentName.compareTo(b.componentName);
-                    if (result == 0) {
-                        // If the two apps are the same component, then prioritize by the order that
-                        // the app user was created (prioritizing the main user's apps)
-                        if (UserHandleCompat.myUserHandle().equals(a.user)) {
-                            return -1;
-                        } else {
-                            Long aUserSerial = getAndCacheUserSerial(a.user);
-                            Long bUserSerial = getAndCacheUserSerial(b.user);
-                            return aUserSerial.compareTo(bUserSerial);
-                        }
-                    }
-                }
-                return result;
-            }
-        };
-        mSectionNameComparator = new Comparator<String>() {
-            @Override
-            public int compare(String o1, String o2) {
-                return compareTitles(o1, o2);
-            }
-        };
-    }
-
-    /**
-     * Returns a locale-aware comparator that will alphabetically order a list of applications.
-     */
-    public Comparator<AppInfo> getAppInfoComparator() {
-        // Clear the user serial cache so that we get serials as needed in the comparator
-        mUserSerialCache.clear();
-        return mAppInfoComparator;
-    }
-
-    /**
-     * Returns a locale-aware comparator that will alphabetically order a list of section names.
-     */
-    public Comparator<String> getSectionNameComparator() {
-        return mSectionNameComparator;
-    }
-
-    /**
-     * Compares two titles with the same return value semantics as Comparator.
-     */
-    private int compareTitles(String titleA, String titleB) {
-        // Ensure that we de-prioritize any titles that don't start with a linguistic letter or digit
-        boolean aStartsWithLetter = Character.isLetterOrDigit(titleA.codePointAt(0));
-        boolean bStartsWithLetter = Character.isLetterOrDigit(titleB.codePointAt(0));
-        if (aStartsWithLetter && !bStartsWithLetter) {
-            return -1;
-        } else if (!aStartsWithLetter && bStartsWithLetter) {
-            return 1;
-        }
-
-        // Order by the title in the current locale
-        return mCollator.compare(titleA, titleB);
-    }
-
-    /**
-     * 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;
-    }
-}
-
 /**
  * The alphabetically sorted list of applications.
  */
diff --git a/src/com/android/launcher3/AppsContainerRecyclerView.java b/src/com/android/launcher3/AppsContainerRecyclerView.java
index 5b1da4b..31942b3 100644
--- a/src/com/android/launcher3/AppsContainerRecyclerView.java
+++ b/src/com/android/launcher3/AppsContainerRecyclerView.java
@@ -122,30 +122,20 @@
         mApps = apps;
     }
 
-    @Override
-    public void setAdapter(Adapter adapter) {
-        // Register a change listener to update the scroll position state whenever the data set
-        // changes.
-        adapter.registerAdapterDataObserver(new AdapterDataObserver() {
-            @Override
-            public void onChanged() {
-                post(new Runnable() {
-                    @Override
-                    public void run() {
-                        refreshCurScrollPosition();
-                    }
-                });
-            }
-        });
-        super.setAdapter(adapter);
-    }
-
     /**
      * Sets the number of apps per row in this recycler view.
      */
     public void setNumAppsPerRow(int numAppsPerRow, int numPredictedAppsPerRow) {
         mNumAppsPerRow = numAppsPerRow;
         mNumPredictedAppsPerRow = numPredictedAppsPerRow;
+
+        DeviceProfile grid = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile();
+        RecyclerView.RecycledViewPool pool = getRecycledViewPool();
+        int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx);
+        pool.setMaxRecycledViews(AppsGridAdapter.PREDICTION_BAR_SPACER_TYPE, 1);
+        pool.setMaxRecycledViews(AppsGridAdapter.EMPTY_VIEW_TYPE, 1);
+        pool.setMaxRecycledViews(AppsGridAdapter.ICON_VIEW_TYPE, approxRows * mNumAppsPerRow);
+        pool.setMaxRecycledViews(AppsGridAdapter.SECTION_BREAK_VIEW_TYPE, approxRows);
     }
 
     public void updateBackgroundPadding(Drawable background) {
@@ -186,7 +176,20 @@
      */
     public void scrollToTop() {
         scrollToPosition(0);
-        updateScrollY(0);
+    }
+
+    /**
+     * Returns the current scroll position.
+     */
+    public int getScrollPosition() {
+        List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
+        getCurScrollState(mScrollPosState, items);
+        if (mScrollPosState.rowIndex != -1) {
+            int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight;
+            return getPaddingTop() + (mScrollPosState.rowIndex * mScrollPosState.rowHeight) +
+                    predictionBarHeight - mScrollPosState.rowTopOffset;
+        }
+        return 0;
     }
 
     @Override
@@ -357,7 +360,6 @@
             if (touchFraction <= predictionBarFraction) {
                 // Scroll to the top of the view, where the prediction bar is
                 layoutManager.scrollToPositionWithOffset(0, 0);
-                updateScrollY(0);
                 return "";
             }
         }
@@ -376,9 +378,6 @@
             lastScrollSection = scrollSection;
         }
 
-        // We need to workaround the RecyclerView to get the right scroll position
-        refreshCurScrollPosition();
-
         // Scroll to the view at the position, anchored at the top of the screen. We call the scroll
         // method on the LayoutManager directly since it is not exposed by RecyclerView.
         layoutManager.scrollToPositionWithOffset(lastScrollSection.appItem.position, 0);
@@ -402,7 +401,6 @@
         int x;
         int y;
         int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight;
-        boolean isRtl = Utilities.isRtl(getResources());
         int rowCount = getNumRows();
         getCurScrollState(mScrollPosState, items);
         if (mScrollPosState.rowIndex != -1) {
@@ -413,7 +411,7 @@
                         (int) (height / ((float) totalScrollHeight / height)));
 
                 // Calculate the position and size of the scroll bar
-                if (isRtl) {
+                if (Utilities.isRtl(getResources())) {
                     x = mBackgroundPadding.left;
                 } else {
                     x = getWidth() - mBackgroundPadding.right - mScrollbarWidth;
@@ -442,11 +440,10 @@
         if (mFastScrollAlpha > 0f && !mFastScrollSectionName.isEmpty()) {
             int x;
             int y;
-            boolean isRtl = Utilities.isRtl(getResources());
 
             // Calculate the position for the fast scroller popup
             Rect bgBounds = mFastScrollerBg.getBounds();
-            if (isRtl) {
+            if (Utilities.isRtl(getResources())) {
                 x = mBackgroundPadding.left + getScrollBarSize();
             } else {
                 x = getWidth() - getPaddingRight() - getScrollBarSize() - bgBounds.width();
@@ -492,20 +489,6 @@
     }
 
     /**
-     * Forces a refresh of the scroll position to any scroll listener.
-     */
-    private void refreshCurScrollPosition() {
-        List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
-        getCurScrollState(mScrollPosState, items);
-        if (mScrollPosState.rowIndex != -1) {
-            int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight;
-            int scrollY = getPaddingTop() + (mScrollPosState.rowIndex * mScrollPosState.rowHeight) +
-                    predictionBarHeight - mScrollPosState.rowTopOffset;
-            updateScrollY(scrollY);
-        }
-    }
-
-    /**
      * Returns the current scroll state.
      */
     private void getCurScrollState(ScrollPositionState stateOut,
diff --git a/src/com/android/launcher3/AppsContainerView.java b/src/com/android/launcher3/AppsContainerView.java
index 8709101..5ccb6c6 100644
--- a/src/com/android/launcher3/AppsContainerView.java
+++ b/src/com/android/launcher3/AppsContainerView.java
@@ -35,6 +35,7 @@
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.FrameLayout;
@@ -47,12 +48,97 @@
 
 
 /**
+ * Interface for controlling the header elevation in response to RecyclerView scroll.
+ */
+interface HeaderElevationController {
+    void onScroll(int scrollY);
+    void disable();
+}
+
+/**
+ * Implementation of the header elevation mechanism for pre-L devices.  It simulates elevation
+ * by drawing a gradient under the header bar.
+ */
+final class HeaderElevationControllerV16 implements HeaderElevationController {
+
+    private final View mShadow;
+
+    private final float mScrollToElevation;
+
+    public HeaderElevationControllerV16(View header) {
+        Resources res = header.getContext().getResources();
+        mScrollToElevation = res.getDimension(R.dimen.all_apps_header_scroll_to_elevation);
+
+        mShadow = new View(header.getContext());
+        mShadow.setBackground(new GradientDrawable(
+                GradientDrawable.Orientation.TOP_BOTTOM, new int[] {0x44000000, 0x00000000}));
+        mShadow.setAlpha(0);
+
+        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
+                FrameLayout.LayoutParams.MATCH_PARENT,
+                res.getDimensionPixelSize(R.dimen.all_apps_header_shadow_height));
+        lp.topMargin = ((FrameLayout.LayoutParams) header.getLayoutParams()).height;
+
+        ((ViewGroup) header.getParent()).addView(mShadow, lp);
+    }
+
+    @Override
+    public void onScroll(int scrollY) {
+        float elevationPct = (float) Math.min(scrollY, mScrollToElevation) /
+                mScrollToElevation;
+        mShadow.setAlpha(elevationPct);
+    }
+
+    @Override
+    public void disable() {
+        ViewGroup parent = (ViewGroup) mShadow.getParent();
+        if (parent != null) {
+            parent.removeView(mShadow);
+        }
+    }
+}
+
+/**
+ * Implementation of the header elevation mechanism for L+ devices, which makes use of the native
+ * view elevation.
+ */
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+final class HeaderElevationControllerVL implements HeaderElevationController {
+
+    private final View mHeader;
+    private final float mMaxElevation;
+    private final float mScrollToElevation;
+
+    public HeaderElevationControllerVL(View header) {
+        mHeader = header;
+
+        Resources res = header.getContext().getResources();
+        mMaxElevation = res.getDimension(R.dimen.all_apps_header_max_elevation);
+        mScrollToElevation = res.getDimension(R.dimen.all_apps_header_scroll_to_elevation);
+    }
+
+    @Override
+    public void onScroll(int scrollY) {
+        float elevationPct = (float) Math.min(scrollY, mScrollToElevation) /
+                mScrollToElevation;
+        float newElevation = mMaxElevation * elevationPct;
+        if (Float.compare(mHeader.getElevation(), newElevation) != 0) {
+            mHeader.setElevation(newElevation);
+        }
+    }
+
+    @Override
+    public void disable() { }
+}
+
+/**
  * The all apps view container.
  */
 public class AppsContainerView extends BaseContainerView implements DragSource, Insettable,
         TextWatcher, TextView.OnEditorActionListener, LauncherTransitionable,
         AlphabeticalAppsList.FilterChangedCallback, AppsGridAdapter.PredictionBarSpacerCallbacks,
-        View.OnTouchListener, View.OnClickListener, View.OnLongClickListener {
+        View.OnTouchListener, View.OnClickListener, View.OnLongClickListener,
+        ViewTreeObserver.OnPreDrawListener {
 
     public static final boolean GRID_MERGE_SECTIONS = true;
 
@@ -96,8 +182,7 @@
     // Normal container insets
     private int mContainerInset;
     private int mPredictionBarHeight;
-    // RecyclerView scroll position
-    @Thunk int mRecyclerViewScrollY;
+    private int mLastRecyclerViewScrollPos = -1;
 
     private CheckLongPressHelper mPredictionIconCheckForLongPress;
     private View mPredictionIconUnderTouch;
@@ -266,14 +351,6 @@
         mAppsRecyclerView.setLayoutManager(mLayoutManager);
         mAppsRecyclerView.setAdapter(mAdapter);
         mAppsRecyclerView.setHasFixedSize(true);
-        mAppsRecyclerView.setOnScrollListenerProxy(
-                new BaseContainerRecyclerView.OnScrollToListener() {
-                    @Override
-                    public void onScrolledTo(int x, int y) {
-                        mRecyclerViewScrollY = y;
-                        onRecyclerViewScrolled();
-                    }
-                });
         if (mItemDecoration != null) {
             mAppsRecyclerView.addItemDecoration(mItemDecoration);
         }
@@ -283,9 +360,7 @@
 
     @Override
     public void onBindPredictionBar() {
-        if (!updatePredictionBarVisibility()) {
-            return;
-        }
+        updatePredictionBarVisibility();
 
         List<AppInfo> predictedApps = mApps.getPredictedApps();
         int childCount = mPredictionBarView.getChildCount();
@@ -401,6 +476,12 @@
     }
 
     @Override
+    public boolean onPreDraw() {
+        synchronizeToRecyclerViewScrollPosition(mAppsRecyclerView.getScrollPosition());
+        return true;
+    }
+
+    @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         return handleTouchEvent(ev);
     }
@@ -600,7 +681,11 @@
 
     @Override
     public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
-        // Do nothing
+        // Register for a pre-draw listener to synchronize the recycler view scroll to other views
+        // in this container
+        if (!toWorkspace) {
+            getViewTreeObserver().addOnPreDrawListener(this);
+        }
     }
 
     @Override
@@ -620,18 +705,26 @@
                 hideSearchField(false, false);
             }
         }
+        if (toWorkspace) {
+            getViewTreeObserver().removeOnPreDrawListener(this);
+            mLastRecyclerViewScrollPos = -1;
+        }
     }
 
     /**
      * Updates the container when the recycler view is scrolled.
      */
     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
-    private void onRecyclerViewScrolled() {
-        if (DYNAMIC_HEADER_ELEVATION) {
-            mElevationController.onScroll(mRecyclerViewScrollY);
-        }
+    private void synchronizeToRecyclerViewScrollPosition(int scrollY) {
+        if (mLastRecyclerViewScrollPos != scrollY) {
+            mLastRecyclerViewScrollPos = scrollY;
+            if (DYNAMIC_HEADER_ELEVATION) {
+                mElevationController.onScroll(scrollY);
+            }
 
-        mPredictionBarView.setTranslationY(-mRecyclerViewScrollY + mAppsRecyclerView.getPaddingTop());
+            // Scroll the prediction bar with the contents of the recycler view
+            mPredictionBarView.setTranslationY(-scrollY + mAppsRecyclerView.getPaddingTop());
+        }
     }
 
     @Override
@@ -686,6 +779,19 @@
                     }
                 }
                 break;
+            case MotionEvent.ACTION_MOVE:
+                if (mPredictionIconUnderTouch != null) {
+                    float dist = (float) Math.hypot(x - mPredictionIconTouchDownPos.x,
+                            y - mPredictionIconTouchDownPos.y);
+                    if (dist > ViewConfiguration.get(getContext()).getScaledTouchSlop()) {
+                        if (mPredictionIconCheckForLongPress != null) {
+                            mPredictionIconCheckForLongPress.cancelLongPress();
+                        }
+                        mPredictionIconCheckForLongPress = null;
+                        mPredictionIconUnderTouch = null;
+                    }
+                }
+                break;
             case MotionEvent.ACTION_UP:
                 if (mBoundsCheckLastTouchDownPos.x > -1) {
                     ViewConfiguration viewConfig = ViewConfiguration.get(getContext());
@@ -728,8 +834,19 @@
      * Returns the predicted app in the prediction bar given a set of local coordinates.
      */
     private View findPredictedAppAtCoordinate(int x, int y) {
-        int[] coord = {x, y};
         Rect hitRect = new Rect();
+
+        // Ensure we aren't hitting the search bar
+        int[] coord = {x, y};
+        Utilities.mapCoordInSelfToDescendent(mHeaderView, this, coord);
+        mHeaderView.getHitRect(hitRect);
+        if (hitRect.contains(coord[0], coord[1])) {
+            return null;
+        }
+
+        // Check against the children of the prediction bar
+        coord[0] = x;
+        coord[1] = y;
         Utilities.mapCoordInSelfToDescendent(mPredictionBarView, this, coord);
         for (int i = 0; i < mPredictionBarView.getChildCount(); i++) {
             View child = mPredictionBarView.getChildAt(i);
@@ -843,79 +960,4 @@
     private InputMethodManager getInputMethodManager() {
         return (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
     }
-
-    private static interface HeaderElevationController {
-
-        public void onScroll(int scrollY);
-
-        public void disable();
-    }
-
-    private static final class HeaderElevationControllerV16 implements HeaderElevationController {
-
-        private final View mShadow;
-
-        private final float mScrollToElevation;
-
-        public HeaderElevationControllerV16(View header) {
-            Resources res = header.getContext().getResources();
-            mScrollToElevation = res.getDimension(R.dimen.all_apps_header_scroll_to_elevation);
-
-            mShadow = new View(header.getContext());
-            mShadow.setBackground(new GradientDrawable(
-                    GradientDrawable.Orientation.TOP_BOTTOM, new int[] {0x44000000, 0x00000000}));
-            mShadow.setAlpha(0);
-
-            FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
-                    LayoutParams.MATCH_PARENT,
-                    res.getDimensionPixelSize(R.dimen.all_apps_header_shadow_height));
-            lp.topMargin = ((FrameLayout.LayoutParams) header.getLayoutParams()).height;
-
-            ((ViewGroup) header.getParent()).addView(mShadow, lp);
-        }
-
-        @Override
-        public void onScroll(int scrollY) {
-            float elevationPct = (float) Math.min(scrollY, mScrollToElevation) /
-                    mScrollToElevation;
-            mShadow.setAlpha(elevationPct);
-        }
-
-        @Override
-        public void disable() {
-            ViewGroup parent = (ViewGroup) mShadow.getParent();
-            if (parent != null) {
-                parent.removeView(mShadow);
-            }
-        }
-    }
-
-    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
-    private static final class HeaderElevationControllerVL implements HeaderElevationController {
-
-        private final View mHeader;
-        private final float mMaxElevation;
-        private final float mScrollToElevation;
-
-        public HeaderElevationControllerVL(View header) {
-            mHeader = header;
-
-            Resources res = header.getContext().getResources();
-            mMaxElevation = res.getDimension(R.dimen.all_apps_header_max_elevation);
-            mScrollToElevation = res.getDimension(R.dimen.all_apps_header_scroll_to_elevation);
-        }
-
-        @Override
-        public void onScroll(int scrollY) {
-            float elevationPct = (float) Math.min(scrollY, mScrollToElevation) /
-                    mScrollToElevation;
-            float newElevation = mMaxElevation * elevationPct;
-            if (Float.compare(mHeader.getElevation(), newElevation) != 0) {
-                mHeader.setElevation(newElevation);
-            }
-        }
-
-        @Override
-        public void disable() { }
-    }
 }
diff --git a/src/com/android/launcher3/AppsGridAdapter.java b/src/com/android/launcher3/AppsGridAdapter.java
index d5a411e..580930c 100644
--- a/src/com/android/launcher3/AppsGridAdapter.java
+++ b/src/com/android/launcher3/AppsGridAdapter.java
@@ -1,3 +1,18 @@
+/*
+ * 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;
diff --git a/src/com/android/launcher3/AppsRecyclerViewContainer.java b/src/com/android/launcher3/AppsRecyclerViewContainer.java
new file mode 100644
index 0000000..cf4beca
--- /dev/null
+++ b/src/com/android/launcher3/AppsRecyclerViewContainer.java
@@ -0,0 +1,61 @@
+/*
+ * 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.Bitmap;
+import android.util.AttributeSet;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler;
+
+public class AppsRecyclerViewContainer extends FrameLayout implements BubbleTextShadowHandler {
+
+    private final ClickShadowView mTouchFeedbackView;
+
+    public AppsRecyclerViewContainer(Context context) {
+        this(context, null);
+    }
+
+    public AppsRecyclerViewContainer(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public AppsRecyclerViewContainer(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+
+        mTouchFeedbackView = new ClickShadowView(context);
+
+        // Make the feedback view large enough to hold the blur bitmap.
+        int size = grid.allAppsIconSizePx + mTouchFeedbackView.getExtraSize();
+        addView(mTouchFeedbackView, size, size);
+    }
+
+    @Override
+    public void setPressedIcon(BubbleTextView icon, Bitmap background) {
+        if (icon == null || background == null) {
+            mTouchFeedbackView.setBitmap(null);
+            mTouchFeedbackView.animate().cancel();
+        } else if (mTouchFeedbackView.setBitmap(background)) {
+            mTouchFeedbackView.alignWithIconView(icon, (ViewGroup) icon.getParent());
+            mTouchFeedbackView.animateShadow();
+        }
+    }
+}
diff --git a/src/com/android/launcher3/BaseContainerRecyclerView.java b/src/com/android/launcher3/BaseContainerRecyclerView.java
index 59e20ca..e52d887 100644
--- a/src/com/android/launcher3/BaseContainerRecyclerView.java
+++ b/src/com/android/launcher3/BaseContainerRecyclerView.java
@@ -29,20 +29,11 @@
 public class BaseContainerRecyclerView extends RecyclerView
         implements RecyclerView.OnItemTouchListener {
 
-    /**
-     * Listener to get notified when the absolute scroll changes.
-     */
-    public interface OnScrollToListener {
-        void onScrolledTo(int x, int y);
-    }
-
     private static final int SCROLL_DELTA_THRESHOLD_DP = 4;
 
     /** Keeps the last known scrolling delta/velocity along y-axis. */
     @Thunk int mDy = 0;
-    @Thunk int mScrollY;
     private float mDeltaThreshold;
-    private OnScrollToListener mScrollToListener;
 
     public BaseContainerRecyclerView(Context context) {
         this(context, null);
@@ -68,20 +59,9 @@
         @Override
         public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
             mDy = dy;
-            mScrollY += dy;
-            if (mScrollToListener != null) {
-                mScrollToListener.onScrolledTo(0, mScrollY);
-            }
         }
     }
 
-    /**
-     * Sets an additional scroll listener, only needed for LMR1 version of the support lib.
-     */
-    public void setOnScrollListenerProxy(OnScrollToListener listener) {
-        mScrollToListener = listener;
-    }
-
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
@@ -106,17 +86,6 @@
     }
 
     /**
-     * Updates the scroll position, used to workaround a RecyclerView issue with scrolling to
-     * position.
-     */
-    protected void updateScrollY(int scrollY) {
-        mScrollY = scrollY;
-        if (mScrollToListener != null) {
-            mScrollToListener.onScrolledTo(0, mScrollY);
-        }
-    }
-
-    /**
      * Returns whether this {@link MotionEvent} should trigger the scroll to be stopped.
      */
     protected boolean shouldStopScroll(MotionEvent ev) {
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 5dc3b12..3b3b9bf 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -28,11 +28,13 @@
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.SparseArray;
 import android.util.TypedValue;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
+import android.view.ViewParent;
 import android.widget.TextView;
 
 import com.android.launcher3.IconCache.IconLoadRequest;
@@ -76,6 +78,7 @@
 
     private boolean mStayPressed;
     private boolean mIgnorePressedStateChange;
+    private boolean mDisableRelayout = false;
 
     private IconLoadRequest mIconLoadRequest;
 
@@ -274,9 +277,10 @@
         }
 
         // Only show the shadow effect when persistent pressed state is set.
-        if (getParent() instanceof ShortcutAndWidgetContainer) {
-            CellLayout layout = (CellLayout) getParent().getParent();
-            layout.setPressedIcon(this, mPressedBackground);
+        ViewParent parent = getParent();
+        if (parent != null && parent.getParent() instanceof BubbleTextShadowHandler) {
+            ((BubbleTextShadowHandler) parent.getParent()).setPressedIcon(
+                    this, mPressedBackground);
         }
 
         updateIconState();
@@ -464,12 +468,20 @@
         return icon;
     }
 
+    @Override
+    public void requestLayout() {
+        if (!mDisableRelayout) {
+            super.requestLayout();
+        }
+    }
+
     /**
      * Applies the item info if it is same as what the view is pointing to currently.
      */
     public void reapplyItemInfo(final ItemInfo info) {
         if (getTag() == info) {
             mIconLoadRequest = null;
+            mDisableRelayout = true;
             if (info instanceof AppInfo) {
                 applyFromApplicationInfo((AppInfo) info);
             } else if (info instanceof ShortcutInfo) {
@@ -478,6 +490,7 @@
             } else if (info instanceof PackageItemInfo) {
                 applyFromPackageItemInfo((PackageItemInfo) info);
             }
+            mDisableRelayout = false;
         }
     }
 
@@ -509,4 +522,11 @@
             }
         }
     }
+
+    /**
+     * Interface to be implemented by the grand parent to allow click shadow effect.
+     */
+    public static interface BubbleTextShadowHandler {
+        void setPressedIcon(BubbleTextView icon, Bitmap background);
+    }
 }
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index a348008..57db805 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -47,6 +47,7 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.DecelerateInterpolator;
 
+import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler;
 import com.android.launcher3.FolderIcon.FolderRingAnimator;
 import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
 import com.android.launcher3.accessibility.FolderAccessibilityHelper;
@@ -61,7 +62,7 @@
 import java.util.HashMap;
 import java.util.Stack;
 
-public class CellLayout extends ViewGroup {
+public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
     public static final int WORKSPACE_ACCESSIBILITY_DRAG = 2;
     public static final int FOLDER_ACCESSIBILITY_DRAG = 1;
 
@@ -409,7 +410,8 @@
         invalidate();
     }
 
-    void setPressedIcon(BubbleTextView icon, Bitmap background) {
+    @Override
+    public void setPressedIcon(BubbleTextView icon, Bitmap background) {
         if (icon == null || background == null) {
             mTouchFeedbackView.setBitmap(null);
             mTouchFeedbackView.animate().cancel();
diff --git a/src/com/android/launcher3/DragController.java b/src/com/android/launcher3/DragController.java
index f5c29ae..2191455 100644
--- a/src/com/android/launcher3/DragController.java
+++ b/src/com/android/launcher3/DragController.java
@@ -174,19 +174,18 @@
      * @param dragInfo The data associated with the object that is being dragged
      * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or
      *        {@link #DRAG_ACTION_COPY}
+     * @param viewImageBounds the position of the image inside the view
      * @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
      *          Makes dragging feel more precise, e.g. you can clip out a transparent border
      */
-    public void startDrag(View v, Bitmap bmp, DragSource source, Object dragInfo, int dragAction,
-            Point extraPadding, float initialDragViewScale) {
+    public void startDrag(View v, Bitmap bmp, DragSource source, Object dragInfo,
+            Rect viewImageBounds, int dragAction, float initialDragViewScale) {
         int[] loc = mCoordinatesTemp;
         mLauncher.getDragLayer().getLocationInDragLayer(v, loc);
-        int viewExtraPaddingLeft = extraPadding != null ? extraPadding.x : 0;
-        int viewExtraPaddingTop = extraPadding != null ? extraPadding.y : 0;
-        int dragLayerX = loc[0] + v.getPaddingLeft() + viewExtraPaddingLeft +
-                (int) ((initialDragViewScale * bmp.getWidth() - bmp.getWidth()) / 2);
-        int dragLayerY = loc[1] + v.getPaddingTop() + viewExtraPaddingTop +
-                (int) ((initialDragViewScale * bmp.getHeight() - bmp.getHeight()) / 2);
+        int dragLayerX = loc[0] + viewImageBounds.left
+                + (int) ((initialDragViewScale * bmp.getWidth() - bmp.getWidth()) / 2);
+        int dragLayerY = loc[1] + viewImageBounds.top
+                + (int) ((initialDragViewScale * bmp.getHeight() - bmp.getHeight()) / 2);
 
         startDrag(bmp, dragLayerX, dragLayerY, source, dragInfo, dragAction, null,
                 null, initialDragViewScale, false);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index a828b1a..51ba2df 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -134,7 +134,7 @@
                    View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener,
                    LauncherStateTransitionAnimation.Callbacks {
     static final String TAG = "Launcher";
-    static final boolean LOGD = true;
+    static final boolean LOGD = false;
 
     // Temporary flag
     static final boolean DISABLE_ALL_APPS_SEARCH_INTEGRATION = true;
@@ -185,10 +185,6 @@
     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: boolean
-    private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME = "launcher.rename_folder";
-    // Type: long
-    private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME_ID = "launcher.rename_folder_id";
     // Type: int
     private static final String RUNTIME_STATE_PENDING_ADD_SPAN_X = "launcher.add_span_x";
     // Type: int
@@ -261,8 +257,6 @@
 
     private int[] mTmpAddItemCellCoordinates = new int[2];
 
-    private FolderInfo mFolderInfo;
-
     private Hotseat mHotseat;
     private ViewGroup mOverviewPanel;
 
@@ -480,11 +474,11 @@
             if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) {
                 // If the user leaves launcher, then we should just load items asynchronously when
                 // they return.
-                mModel.startLoader(true, PagedView.INVALID_RESTORE_PAGE);
+                mModel.startLoader(PagedView.INVALID_RESTORE_PAGE);
             } else {
                 // We only load the page synchronously if the user rotates (or triggers a
                 // configuration change) while launcher is in the foreground
-                mModel.startLoader(true, mWorkspace.getRestorePage());
+                mModel.startLoader(mWorkspace.getRestorePage());
             }
         }
 
@@ -1051,7 +1045,7 @@
         mPaused = false;
         if (mRestoring || mOnResumeNeedsLoad) {
             setWorkspaceLoading(true);
-            mModel.startLoader(true, PagedView.INVALID_RESTORE_PAGE);
+            mModel.startLoader(PagedView.INVALID_RESTORE_PAGE);
             mRestoring = false;
             mOnResumeNeedsLoad = false;
         }
@@ -1385,13 +1379,6 @@
             mRestoring = true;
         }
 
-        boolean renameFolder = savedState.getBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, false);
-        if (renameFolder) {
-            long id = savedState.getLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID);
-            mFolderInfo = mModel.getFolderById(this, sFolders, id);
-            mRestoring = true;
-        }
-
         mItemIdToViewId = (HashMap<Integer, Integer>)
                 savedState.getSerializable(RUNTIME_STATE_VIEW_IDS);
     }
@@ -1703,11 +1690,11 @@
                 updateAutoAdvanceState();
             } else if (ENABLE_DEBUG_INTENTS && DebugIntents.DELETE_DATABASE.equals(action)) {
                 mModel.resetLoadedState(false, true);
-                mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE,
+                mModel.startLoader(PagedView.INVALID_RESTORE_PAGE,
                         LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE);
             } else if (ENABLE_DEBUG_INTENTS && DebugIntents.MIGRATE_DATABASE.equals(action)) {
                 mModel.resetLoadedState(false, true);
-                mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE,
+                mModel.startLoader(PagedView.INVALID_RESTORE_PAGE,
                         LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE
                                 | LauncherModel.LOADER_FLAG_MIGRATE_SHORTCUTS);
             }
@@ -2035,11 +2022,6 @@
             outState.putInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID, mPendingAddWidgetId);
         }
 
-        if (mFolderInfo != null && mWaitingForResult) {
-            outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true);
-            outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id);
-        }
-
         // Save the current widgets tray?
         // TODO(hyunyoungs)
         outState.putSerializable(RUNTIME_STATE_VIEW_IDS, mItemIdToViewId);
@@ -3431,7 +3413,7 @@
      * Shows the widgets view.
      */
     void showWidgetsView(boolean animated, boolean resetPageToZero) {
-        Log.d(TAG, "showWidgetsView:" + animated + " resetPageToZero:" + resetPageToZero);
+        if (LOGD) Log.d(TAG, "showWidgetsView:" + animated + " resetPageToZero:" + resetPageToZero);
         if (resetPageToZero) {
             mWidgetsView.scrollToTop();
         }
@@ -3497,8 +3479,7 @@
     }
 
     public void enterSpringLoadedDragMode() {
-        Log.d(TAG, String.format("enterSpringLoadedDragMode [mState=%s",
-                mState.name()));
+        if (LOGD) Log.d(TAG, String.format("enterSpringLoadedDragMode [mState=%s", mState.name()));
         if (mState == State.WORKSPACE || mState == State.APPS_SPRING_LOADED ||
                 mState == State.WIDGETS_SPRING_LOADED) {
             return;
@@ -3693,7 +3674,7 @@
      */
     private boolean waitUntilResume(Runnable run, boolean deletePreviousRunnables) {
         if (mPaused) {
-            Log.i(TAG, "Deferring update until onResume");
+            if (LOGD) Log.d(TAG, "Deferring update until onResume");
             if (deletePreviousRunnables) {
                 while (mBindOnResumeCallbacks.remove(run)) {
                 }
@@ -3729,7 +3710,7 @@
      */
     public boolean setLoadOnResume() {
         if (mPaused) {
-            Log.i(TAG, "setLoadOnResume");
+            if (LOGD) Log.d(TAG, "setLoadOnResume");
             mOnResumeNeedsLoad = true;
             return true;
         } else {
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index d51df32..bde54c3 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -33,11 +33,9 @@
 
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.PackageInstallerCompat;
-import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
 import com.android.launcher3.util.Thunk;
 
 import java.lang.ref.WeakReference;
-import java.util.ArrayList;
 
 public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks {
 
diff --git a/src/com/android/launcher3/LauncherClings.java b/src/com/android/launcher3/LauncherClings.java
index 63d3ebc..c13752c 100644
--- a/src/com/android/launcher3/LauncherClings.java
+++ b/src/com/android/launcher3/LauncherClings.java
@@ -71,7 +71,7 @@
             // Copy the shortcuts from the old database
             LauncherModel model = mLauncher.getModel();
             model.resetLoadedState(false, true);
-            model.startLoader(false, PagedView.INVALID_RESTORE_PAGE,
+            model.startLoader(PagedView.INVALID_RESTORE_PAGE,
                     LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE
                             | LauncherModel.LOADER_FLAG_MIGRATE_SHORTCUTS);
             // Set the flag to skip the folder cling
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index c56022e..58e899a 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -66,7 +66,6 @@
 import java.lang.ref.WeakReference;
 import java.net.URISyntaxException;
 import java.security.InvalidParameterException;
-import java.text.Collator;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -1346,40 +1345,32 @@
             }
         }
         if (runLoader) {
-            startLoader(false, PagedView.INVALID_RESTORE_PAGE);
+            startLoader(PagedView.INVALID_RESTORE_PAGE);
         }
     }
 
-    // If there is already a loader task running, tell it to stop.
-    // returns true if isLaunching() was true on the old task
-    private boolean stopLoaderLocked() {
-        boolean isLaunching = false;
+    /**
+     * If there is already a loader task running, tell it to stop.
+     */
+    private void stopLoaderLocked() {
         LoaderTask oldTask = mLoaderTask;
         if (oldTask != null) {
-            if (oldTask.isLaunching()) {
-                isLaunching = true;
-            }
             oldTask.stopLocked();
         }
-        return isLaunching;
     }
 
     public boolean isCurrentCallbacks(Callbacks callbacks) {
         return (mCallbacks != null && mCallbacks.get() == callbacks);
     }
 
-    public void startLoader(boolean isLaunching, int synchronousBindPage) {
-        startLoader(isLaunching, synchronousBindPage, LOADER_FLAG_NONE);
+    public void startLoader(int synchronousBindPage) {
+        startLoader(synchronousBindPage, LOADER_FLAG_NONE);
     }
 
-    public void startLoader(boolean isLaunching, int synchronousBindPage, int loadFlags) {
+    public void startLoader(int synchronousBindPage, int loadFlags) {
         // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
         InstallShortcutReceiver.enableInstallQueue();
         synchronized (mLock) {
-            if (DEBUG_LOADERS) {
-                Log.d(TAG, "startLoader isLaunching=" + isLaunching);
-            }
-
             // Clear any deferred bind-runnables from the synchronized load process
             // We must do this before any loading/binding is scheduled below.
             synchronized (mDeferredBindRunnables) {
@@ -1389,11 +1380,10 @@
             // Don't bother to start the thread if we know it's not going to do anything
             if (mCallbacks != null && mCallbacks.get() != null) {
                 // If there is already one running, tell it to stop.
-                // also, don't downgrade isLaunching if we're already running
-                isLaunching = isLaunching || stopLoaderLocked();
-                mLoaderTask = new LoaderTask(mApp.getContext(), isLaunching, loadFlags);
+                stopLoaderLocked();
+                mLoaderTask = new LoaderTask(mApp.getContext(), loadFlags);
                 if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
-                        && mAllAppsLoaded && mWorkspaceLoaded) {
+                        && mAllAppsLoaded && mWorkspaceLoaded && !mIsLoaderTaskRunning) {
                     mLoaderTask.runBindSynchronousPage(synchronousBindPage);
                 } else {
                     sWorkerThread.setPriority(Thread.NORM_PRIORITY);
@@ -1484,22 +1474,16 @@
      */
     private class LoaderTask implements Runnable {
         private Context mContext;
-        private boolean mIsLaunching;
         @Thunk boolean mIsLoadingAndBindingWorkspace;
         private boolean mStopped;
         @Thunk boolean mLoadAndBindStepFinished;
         private int mFlags;
 
-        LoaderTask(Context context, boolean isLaunching, int flags) {
+        LoaderTask(Context context, int flags) {
             mContext = context;
-            mIsLaunching = isLaunching;
             mFlags = flags;
         }
 
-        boolean isLaunching() {
-            return mIsLaunching;
-        }
-
         boolean isLoadingWorkspace() {
             return mIsLoadingAndBindingWorkspace;
         }
@@ -1600,20 +1584,15 @@
 
         public void run() {
             synchronized (mLock) {
+                if (mStopped) {
+                    return;
+                }
                 mIsLoaderTaskRunning = true;
             }
             // Optimize for end-user experience: if the Launcher is up and // running with the
             // All Apps interface in the foreground, load All Apps first. Otherwise, load the
             // workspace first (default).
             keep_running: {
-                // Elevate priority when Home launches for the first time to avoid
-                // starving at boot time. Staring at a blank home is not cool.
-                synchronized (mLock) {
-                    if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to " +
-                            (mIsLaunching ? "DEFAULT" : "BACKGROUND"));
-                    android.os.Process.setThreadPriority(mIsLaunching
-                            ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND);
-                }
                 if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
                 loadAndBindWorkspace();
 
@@ -1621,24 +1600,11 @@
                     break keep_running;
                 }
 
-                // Whew! Hard work done.  Slow us down, and wait until the UI thread has
-                // settled down.
-                synchronized (mLock) {
-                    if (mIsLaunching) {
-                        if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to BACKGROUND");
-                        android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
-                    }
-                }
                 waitForIdle();
 
                 // second step
                 if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
                 loadAndBindAllApps();
-
-                // Restore the default thread priority after we are done loading items
-                synchronized (mLock) {
-                    android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
-                }
             }
 
             // Clear out this reference, otherwise we end up holding it until all of the
@@ -2783,6 +2749,11 @@
             }
             if (!mAllAppsLoaded) {
                 loadAllApps();
+                synchronized (LoaderTask.this) {
+                    if (mStopped) {
+                        return;
+                    }
+                }
                 updateAllAppsIconsCache();
                 synchronized (LoaderTask.this) {
                     if (mStopped) {
@@ -2974,7 +2945,6 @@
         public void dumpState() {
             synchronized (sBgLock) {
                 Log.d(TAG, "mLoaderTask.mContext=" + mContext);
-                Log.d(TAG, "mLoaderTask.mIsLaunching=" + mIsLaunching);
                 Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
                 Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
                 Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size());
@@ -3694,39 +3664,6 @@
         return folderInfo;
     }
 
-    public static class WidgetAndShortcutNameComparator implements Comparator<Object> {
-        private final AppWidgetManagerCompat mManager;
-        private final PackageManager mPackageManager;
-        private final HashMap<Object, String> mLabelCache;
-        private final Collator mCollator;
-
-        public WidgetAndShortcutNameComparator(Context context) {
-            mManager = AppWidgetManagerCompat.getInstance(context);
-            mPackageManager = context.getPackageManager();
-            mLabelCache = new HashMap<Object, String>();
-            mCollator = Collator.getInstance();
-        }
-        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);
-            }
-            return mCollator.compare(labelA, labelB);
-        }
-    };
 
     static boolean isValidProvider(AppWidgetProviderInfo provider) {
         return (provider != null) && (provider.provider != null)
diff --git a/src/com/android/launcher3/model/AppNameComparator.java b/src/com/android/launcher3/model/AppNameComparator.java
new file mode 100644
index 0000000..706f751
--- /dev/null
+++ b/src/com/android/launcher3/model/AppNameComparator.java
@@ -0,0 +1,105 @@
+package com.android.launcher3.model;
+
+import android.content.Context;
+
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
+import java.text.Collator;
+import java.util.Comparator;
+import java.util.HashMap;
+
+/**
+ * Class to manage access to an app name comparator.
+ * <p>
+ * Used to sort application name in all apps view and widget tray view.
+ */
+public class AppNameComparator {
+    private final UserManagerCompat mUserManager;
+    private final Collator mCollator;
+    private final Comparator<ItemInfo> mAppInfoComparator;
+    private final Comparator<String> mSectionNameComparator;
+    private HashMap<UserHandleCompat, Long> mUserSerialCache = new HashMap<>();
+
+    public AppNameComparator(Context context) {
+        mCollator = Collator.getInstance();
+        mUserManager = UserManagerCompat.getInstance(context);
+        mAppInfoComparator = new Comparator<ItemInfo>() {
+
+            public final int compare(ItemInfo a, ItemInfo b) {
+                // Order by the title in the current locale
+                int result = compareTitles(a.title.toString(), b.title.toString());
+                if (result == 0 && a instanceof AppInfo && b instanceof AppInfo) {
+                    AppInfo aAppInfo = (AppInfo) a;
+                    AppInfo bAppInfo = (AppInfo) b;
+                    // If two apps have the same title, then order by the component name
+                    result = aAppInfo.componentName.compareTo(bAppInfo.componentName);
+                    if (result == 0) {
+                        // If the two apps are the same component, then prioritize by the order that
+                        // the app user was created (prioritizing the main user's apps)
+                        if (UserHandleCompat.myUserHandle().equals(a.user)) {
+                            return -1;
+                        } else {
+                            Long aUserSerial = getAndCacheUserSerial(a.user);
+                            Long bUserSerial = getAndCacheUserSerial(b.user);
+                            return aUserSerial.compareTo(bUserSerial);
+                        }
+                    }
+                }
+                return result;
+            }
+        };
+        mSectionNameComparator = new Comparator<String>() {
+            @Override
+            public int compare(String o1, String o2) {
+                return compareTitles(o1, o2);
+            }
+        };
+    }
+
+    /**
+     * 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
+        mUserSerialCache.clear();
+        return mAppInfoComparator;
+    }
+
+    /**
+     * Returns a locale-aware comparator that will alphabetically order a list of section names.
+     */
+    public Comparator<String> getSectionNameComparator() {
+        return mSectionNameComparator;
+    }
+
+    /**
+     * Compares two titles with the same return value semantics as Comparator.
+     */
+    private int compareTitles(String titleA, String titleB) {
+        // Ensure that we de-prioritize any titles that don't start with a linguistic letter or digit
+        boolean aStartsWithLetter = Character.isLetterOrDigit(titleA.codePointAt(0));
+        boolean bStartsWithLetter = Character.isLetterOrDigit(titleB.codePointAt(0));
+        if (aStartsWithLetter && !bStartsWithLetter) {
+            return -1;
+        } else if (!aStartsWithLetter && bStartsWithLetter) {
+            return 1;
+        }
+
+        // Order by the title in the current locale
+        return mCollator.compare(titleA, titleB);
+    }
+
+    /**
+     * 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;
+    }
+}
diff --git a/src/com/android/launcher3/model/WidgetsAndShortcutNameComparator.java b/src/com/android/launcher3/model/WidgetsAndShortcutNameComparator.java
new file mode 100644
index 0000000..7c4e806
--- /dev/null
+++ b/src/com/android/launcher3/model/WidgetsAndShortcutNameComparator.java
@@ -0,0 +1,63 @@
+package com.android.launcher3.model;
+
+import android.appwidget.AppWidgetProviderInfo;
+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 java.text.Collator;
+import java.util.Comparator;
+import java.util.HashMap;
+
+public class WidgetsAndShortcutNameComparator implements Comparator<Object> {
+    private final AppWidgetManagerCompat mManager;
+    private final PackageManager mPackageManager;
+    private final HashMap<Object, String> mLabelCache;
+    private final Collator mCollator;
+
+    public WidgetsAndShortcutNameComparator(Context context) {
+        mManager = AppWidgetManagerCompat.getInstance(context);
+        mPackageManager = context.getPackageManager();
+        mLabelCache = new HashMap<Object, String>();
+        mCollator = Collator.getInstance();
+    }
+
+    @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);
+        }
+        int result = mCollator.compare(labelA, labelB);
+        if (result == 0 && a instanceof AppWidgetProviderInfo &&
+                b instanceof AppWidgetProviderInfo) {
+            AppWidgetProviderInfo aInfo = (AppWidgetProviderInfo) a;
+            AppWidgetProviderInfo bInfo = (AppWidgetProviderInfo) b;
+
+            // prioritize main user's widgets against work profile widgets.
+            if (aInfo.getProfile().equals(android.os.Process.myUserHandle())) {
+                return -1;
+            } else if (bInfo.getProfile().equals(android.os.Process.myUserHandle())) {
+                return 1;
+            }
+        }
+        return result;
+    }
+};
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index e3eb76c..dcaf1f2 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -21,17 +21,14 @@
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
-import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
 import android.view.View.OnLayoutChangeListener;
-import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
@@ -41,7 +38,13 @@
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 
 /**
- * Represents the individual cell of the widget inside the widget tray.
+ * Represents the individual cell of the widget inside the widget tray. The preview is drawn
+ * horizontally centered, and scaled down if needed.
+ *
+ * This view does not support padding. Since the image is scaled down to fit the view, padding will
+ * further decrease the scaling factor. Drag-n-drop uses the view bounds for showing a smooth
+ * transition from the view to drag view, so when adding padding support, DnD would need to
+ * consider the appropriate scaling factor.
  */
 public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
 
@@ -59,13 +62,11 @@
     private int mPresetPreviewSize;
     int cellSize;
 
-    private ImageView mWidgetImage;
+    private WidgetImageView mWidgetImage;
     private TextView mWidgetName;
     private TextView mWidgetDims;
-    private final Rect mOrigImgPadding = new Rect();
 
     private String mDimensionsFormatString;
-    private boolean mIsAppWidget;
     private Object mInfo;
 
     private WidgetPreviewLoader mWidgetPreviewLoader;
@@ -101,12 +102,7 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
 
-        mWidgetImage = (ImageView) findViewById(R.id.widget_preview);
-        mOrigImgPadding.left = mWidgetImage.getPaddingLeft();
-        mOrigImgPadding.top = mWidgetImage.getPaddingTop();
-        mOrigImgPadding.right = mWidgetImage.getPaddingRight();
-        mOrigImgPadding.bottom = mWidgetImage.getPaddingBottom();
-
+        mWidgetImage = (WidgetImageView) findViewById(R.id.widget_preview);
         mWidgetName = ((TextView) findViewById(R.id.widget_name));
         mWidgetDims = ((TextView) findViewById(R.id.widget_dims));
     }
@@ -119,7 +115,7 @@
             Log.d(TAG, "reset called on:" + mWidgetName.getText());
         }
         mWidgetImage.animate().cancel();
-        mWidgetImage.setImageDrawable(null);
+        mWidgetImage.setBitmap(null);
         mWidgetName.setText(null);
         mWidgetDims.setText(null);
 
@@ -133,15 +129,11 @@
      * Apply the widget provider info to the view.
      */
     public void applyFromAppWidgetProviderInfo(LauncherAppWidgetProviderInfo info,
-            int maxWidth, WidgetPreviewLoader loader) {
+            WidgetPreviewLoader loader) {
         LauncherAppState app = LauncherAppState.getInstance();
         DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
 
-        mIsAppWidget = true;
         mInfo = info;
-        if (maxWidth > -1) {
-            mWidgetImage.setMaxWidth(maxWidth);
-        }
         // TODO(hyunyoungs): setup a cache for these labels.
         mWidgetName.setText(AppWidgetManagerCompat.getInstance(getContext()).loadLabel(info));
         int hSpan = Math.min(info.spanX, grid.numColumns);
@@ -155,7 +147,6 @@
      */
     public void applyFromResolveInfo(
             PackageManager pm, ResolveInfo info, WidgetPreviewLoader loader) {
-        mIsAppWidget = false;
         mInfo = info;
         CharSequence label = info.loadLabel(pm);
         mWidgetName.setText(label);
@@ -172,20 +163,8 @@
     }
 
     public void applyPreview(Bitmap bitmap) {
-        FastBitmapDrawable preview = new FastBitmapDrawable(bitmap);
-
-        if (preview != null) {
-            mWidgetImage.setImageDrawable(preview);
-
-            if (mIsAppWidget) {
-                // center horizontally
-                int[] imageSize = getPreviewSize();
-                int centerAmount = (imageSize[0] - preview.getIntrinsicWidth()) / 2;
-                mWidgetImage.setPadding(mOrigImgPadding.left + centerAmount,
-                        mOrigImgPadding.top,
-                        mOrigImgPadding.right,
-                        mOrigImgPadding.bottom);
-            }
+        if (bitmap != null) {
+            mWidgetImage.setBitmap(bitmap);
             mWidgetImage.setAlpha(0f);
             mWidgetImage.animate().alpha(1.0f).setDuration(FADE_IN_DURATION_MS);
         }
diff --git a/src/com/android/launcher3/widget/WidgetImageView.java b/src/com/android/launcher3/widget/WidgetImageView.java
index f1eaf64..6f8fd89 100644
--- a/src/com/android/launcher3/widget/WidgetImageView.java
+++ b/src/com/android/launcher3/widget/WidgetImageView.java
@@ -17,25 +17,73 @@
 package com.android.launcher3.widget;
 
 import android.content.Context;
+import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
 import android.util.AttributeSet;
-import android.widget.ImageView;
+import android.view.View;
 
-public class WidgetImageView extends ImageView {
+/**
+ * View that draws a bitmap horizontally centered. If the image width is greater than the view
+ * width, the image is scaled down appropriately.
+ */
+public class WidgetImageView extends View {
+
+    private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+    private final RectF mDstRectF = new RectF();
+    private Bitmap mBitmap;
+
+    public WidgetImageView(Context context) {
+        super(context);
+    }
 
     public WidgetImageView(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
+    public WidgetImageView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public void setBitmap(Bitmap bitmap) {
+        mBitmap = bitmap;
+        invalidate();
+    }
+
+    public Bitmap getBitmap() {
+        return mBitmap;
+    }
+
     @Override
     protected void onDraw(Canvas canvas) {
-        canvas.save();
-        canvas.clipRect(getScrollX() + getPaddingLeft(),
-                getScrollY() + getPaddingTop(),
-                getScrollX() + getRight() - getLeft() - getPaddingRight(),
-                getScrollY() + getBottom() - getTop() - getPaddingBottom());
+        if (mBitmap != null) {
+            updateDstRectF();
+            canvas.drawBitmap(mBitmap, null, mDstRectF, mPaint);
+        }
+    }
 
-        super.onDraw(canvas);
-        canvas.restore();
+    private void updateDstRectF() {
+        if (mBitmap.getWidth() > getWidth()) {
+            float scale = ((float) getWidth()) / mBitmap.getWidth();
+            mDstRectF.set(0, 0, getWidth(), scale * mBitmap.getHeight());
+        } else {
+            mDstRectF.set(
+                    (getWidth() - mBitmap.getWidth()) * 0.5f,
+                    0,
+                    (getWidth() + mBitmap.getWidth()) * 0.5f,
+                    mBitmap.getHeight());
+        }
+    }
+
+    /**
+     * @return the bounds where the image was drawn.
+     */
+    public Rect getBitmapBounds() {
+        updateDstRectF();
+        Rect rect = new Rect();
+        mDstRectF.round(rect);
+        return rect;
     }
 }
diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java
index 00fb225..181c08a 100644
--- a/src/com/android/launcher3/widget/WidgetsContainerView.java
+++ b/src/com/android/launcher3/widget/WidgetsContainerView.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.support.v7.widget.LinearLayoutManager;
@@ -28,8 +27,8 @@
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
-import android.widget.ImageView;
 import android.widget.Toast;
+
 import com.android.launcher3.BaseContainerView;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeleteDropTarget;
@@ -37,10 +36,8 @@
 import com.android.launcher3.DragController;
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.Folder;
 import com.android.launcher3.IconCache;
-import com.android.launcher3.Insettable;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
@@ -222,20 +219,19 @@
 
     private boolean beginDraggingWidget(WidgetCell v) {
         // Get the widget preview as the drag representation
-        ImageView image = (ImageView) v.findViewById(R.id.widget_preview);
+        WidgetImageView image = (WidgetImageView) v.findViewById(R.id.widget_preview);
         PendingAddItemInfo createItemInfo = (PendingAddItemInfo) v.getTag();
 
         // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and
         // we abort the drag.
-        if (image.getDrawable() == null) {
+        if (image.getBitmap() == null) {
             return false;
         }
 
         // Compose the drag image
         Bitmap preview;
-        Bitmap outline;
         float scale = 1f;
-        Point previewPadding = null;
+        final Rect bounds = image.getBitmapBounds();
 
         if (createItemInfo instanceof PendingAddWidgetInfo) {
             // This can happen in some weird cases involving multi-touch. We can't start dragging
@@ -244,25 +240,25 @@
             PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) createItemInfo;
             int[] size = mLauncher.getWorkspace().estimateItemSize(createWidgetInfo, true);
 
-            FastBitmapDrawable previewDrawable = (FastBitmapDrawable) image.getDrawable();
+            Bitmap icon = image.getBitmap();
             float minScale = 1.25f;
-            int maxWidth = Math.min((int) (previewDrawable.getIntrinsicWidth() * minScale), size[0]);
+            int maxWidth = Math.min((int) (icon.getWidth() * minScale), size[0]);
 
             int[] previewSizeBeforeScale = new int[1];
             preview = getWidgetPreviewLoader().generateWidgetPreview(createWidgetInfo.info,
                     maxWidth, null, previewSizeBeforeScale);
-            // Compare the size of the drag preview to the preview in the AppsCustomize tray
-            int previewWidthInAppsCustomize = Math.min(previewSizeBeforeScale[0],
-                    v.getActualItemWidth());
-            scale = previewWidthInAppsCustomize / (float) preview.getWidth();
 
-            // The bitmap in the AppsCustomize tray is always the the same size, so there
-            // might be extra pixels around the preview itself - this accounts for that
-            if (previewWidthInAppsCustomize < previewDrawable.getIntrinsicWidth()) {
-                int padding =
-                        (previewDrawable.getIntrinsicWidth() - previewWidthInAppsCustomize) / 2;
-                previewPadding = new Point(padding, 0);
+            if (previewSizeBeforeScale[0] < icon.getWidth()) {
+                // The icon has extra padding around it.
+                int padding = (icon.getWidth() - previewSizeBeforeScale[0]) / 2;
+                if (icon.getWidth() > image.getWidth()) {
+                    padding = padding * image.getWidth() / icon.getWidth();
+                }
+
+                bounds.left += padding;
+                bounds.right -= padding;
             }
+            scale = bounds.width() / (float) preview.getWidth();
         } else {
             PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) v.getTag();
             Drawable icon = mIconCache.getFullResIcon(createShortcutInfo.activityInfo);
@@ -274,16 +270,12 @@
         boolean clipAlpha = !(createItemInfo instanceof PendingAddWidgetInfo &&
                 (((PendingAddWidgetInfo) createItemInfo).previewImage == 0));
 
-        // Save the preview for the outline generation, then dim the preview
-        outline = Bitmap.createScaledBitmap(preview, preview.getWidth(), preview.getHeight(),
-                false);
-
         // Start the drag
         mLauncher.lockScreenOrientation();
-        mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, outline, clipAlpha);
+        mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, preview, clipAlpha);
         mDragController.startDrag(image, preview, this, createItemInfo,
-                DragController.DRAG_ACTION_COPY, previewPadding, scale);
-        outline.recycle();
+                bounds, DragController.DRAG_ACTION_COPY, scale);
+
         preview.recycle();
         return true;
     }
diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java
index 397d177..2f733dc 100644
--- a/src/com/android/launcher3/widget/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java
@@ -18,9 +18,9 @@
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
 import android.os.Build;
 import android.support.v7.widget.RecyclerView;
-import android.content.res.Resources;
 import android.support.v7.widget.RecyclerView.Adapter;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -29,15 +29,16 @@
 import android.view.ViewGroup.LayoutParams;
 import android.view.ViewGroup.MarginLayoutParams;
 import android.widget.LinearLayout;
+
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DynamicGrid;
-import com.android.launcher3.IconCache;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.R;
 import com.android.launcher3.WidgetPreviewLoader;
+
 import java.util.List;
 
 /**
@@ -56,7 +57,6 @@
     private Context mContext;
     private Launcher mLauncher;
     private LayoutInflater mLayoutInflater;
-    private IconCache mIconCache;
 
     private WidgetsModel mWidgetsModel;
     private WidgetPreviewLoader mWidgetPreviewLoader;
@@ -76,9 +76,7 @@
 
         mIconClickListener = iconClickListener;
         mIconLongClickListener = iconLongClickListener;
-
         mLauncher = launcher;
-        mIconCache = LauncherAppState.getInstance().getIconCache();
 
         setContainerHeight();
     }
@@ -143,7 +141,7 @@
                 LauncherAppWidgetProviderInfo info = (LauncherAppWidgetProviderInfo) infoList.get(i);
                 PendingAddWidgetInfo pawi = new PendingAddWidgetInfo(info, null);
                 widget.setTag(pawi);
-                widget.applyFromAppWidgetProviderInfo(info, -1, mWidgetPreviewLoader);
+                widget.applyFromAppWidgetProviderInfo(info, mWidgetPreviewLoader);
             } else if (infoList.get(i) instanceof ResolveInfo) {
                 ResolveInfo info = (ResolveInfo) infoList.get(i);
                 PendingAddShortcutInfo pasi = new PendingAddShortcutInfo(info.activityInfo);
diff --git a/src/com/android/launcher3/widget/WidgetsModel.java b/src/com/android/launcher3/widget/WidgetsModel.java
index 71a7b94..5a920e8 100644
--- a/src/com/android/launcher3/widget/WidgetsModel.java
+++ b/src/com/android/launcher3/widget/WidgetsModel.java
@@ -10,8 +10,9 @@
 import com.android.launcher3.IconCache;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.LauncherModel.WidgetAndShortcutNameComparator;
 import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.model.AppNameComparator;
+import com.android.launcher3.model.WidgetsAndShortcutNameComparator;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -40,12 +41,14 @@
     private RecyclerView.Adapter mAdapter;
 
     private Comparator mWidgetAndShortcutNameComparator;
+    private Comparator mAppNameComparator;
 
     private IconCache mIconCache;
 
     public WidgetsModel(Context context, RecyclerView.Adapter adapter) {
         mAdapter = adapter;
-        mWidgetAndShortcutNameComparator = new WidgetAndShortcutNameComparator(context);
+        mWidgetAndShortcutNameComparator = new WidgetsAndShortcutNameComparator(context);
+        mAppNameComparator = (new AppNameComparator(context)).getAppInfoComparator();
         mIconCache = LauncherAppState.getInstance().getIconCache();
     }
 
@@ -108,7 +111,7 @@
         }
 
         // sort.
-        sortPackageItemInfos();
+        Collections.sort(mPackageItemInfos, mAppNameComparator);
         for (PackageItemInfo p: mPackageItemInfos) {
             Collections.sort(mWidgetsList.get(p), mWidgetAndShortcutNameComparator);
         }
@@ -116,13 +119,4 @@
         // notify.
         mAdapter.notifyDataSetChanged();
     }
-
-    private void sortPackageItemInfos() {
-        Collections.sort(mPackageItemInfos, new Comparator<PackageItemInfo>() {
-            @Override
-            public int compare(PackageItemInfo lhs, PackageItemInfo rhs) {
-                return lhs.title.toString().compareTo(rhs.title.toString());
-            }
-        });
-    }
 }
\ No newline at end of file