Merge "Grid change polish bugs." into ub-launcher3-dorval-polish
diff --git a/res/drawable-xxhdpi/all_apps_alpha_mask.png b/res/drawable/all_apps_alpha_mask.png
similarity index 100%
rename from res/drawable-xxhdpi/all_apps_alpha_mask.png
rename to res/drawable/all_apps_alpha_mask.png
Binary files differ
diff --git a/res/layout-land/all_apps_fast_scroller.xml b/res/layout-land/all_apps_fast_scroller.xml
new file mode 100644
index 0000000..957c331
--- /dev/null
+++ b/res/layout-land/all_apps_fast_scroller.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<merge
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto">
+    <!-- Fast scroller popup -->
+    <TextView
+        android:id="@+id/fast_scroller_popup"
+        style="@style/FastScrollerPopup"
+        android:layout_alignParentEnd="true"
+        android:layout_alignTop="@+id/apps_list_view"
+        android:layout_marginEnd="-5dp" />
+
+    <com.android.launcher3.allapps.LandscapeFastScroller
+        android:id="@+id/fast_scroller"
+        android:layout_width="48dp"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentEnd="true"
+        android:layout_alignTop="@+id/apps_list_view"
+        android:layout_marginEnd="-48dp"
+        android:layout_marginTop="-8dp"
+        launcher:canThumbDetach="true" />
+
+</merge>
\ No newline at end of file
diff --git a/res/layout-sw720dp/all_apps_fast_scroller.xml b/res/layout-sw720dp/all_apps_fast_scroller.xml
new file mode 100644
index 0000000..12c15cc
--- /dev/null
+++ b/res/layout-sw720dp/all_apps_fast_scroller.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<merge
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto">
+    <!-- Fast scroller popup -->
+    <TextView
+        android:id="@+id/fast_scroller_popup"
+        style="@style/FastScrollerPopup"
+        android:layout_alignParentEnd="true"
+        android:layout_alignTop="@+id/apps_list_view"
+        android:layout_marginEnd="@dimen/fastscroll_popup_margin" />
+
+    <com.android.launcher3.views.RecyclerViewFastScroller
+        android:id="@+id/fast_scroller"
+        android:layout_width="@dimen/fastscroll_width"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentEnd="true"
+        android:layout_alignTop="@+id/apps_list_view"
+        android:layout_marginEnd="@dimen/fastscroll_end_margin"
+        launcher:canThumbDetach="true" />
+
+</merge>
\ No newline at end of file
diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml
index a3c2535..93662fc 100644
--- a/res/layout/all_apps.xml
+++ b/res/layout/all_apps.xml
@@ -16,7 +16,8 @@
 <!-- The top and bottom paddings are defined in this container, but since we want
      the list view to span the full width (for touch interception purposes), we
      will bake the left/right padding into that view's background itself. -->
-<com.android.launcher3.allapps.AllAppsContainerView xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.launcher3.allapps.AllAppsContainerView
+    xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:launcher="http://schemas.android.com/apk/res-auto"
     android:id="@+id/apps_view"
     android:layout_width="match_parent"
@@ -39,6 +40,8 @@
         android:layout_height="match_parent"
         android:layout_gravity="center"
         android:focusable="true"
+        android:clipToPadding="false"
+        android:clipChildren="true"
         android:focusableInTouchMode="true"
         android:saveEnabled="false"
         android:visibility="gone">
@@ -55,20 +58,14 @@
             android:descendantFocusability="afterDescendants"
             android:focusable="true" />
 
-        <!-- Fast scroller popup -->
-        <TextView
-            style="@style/FastScrollerPopup"
-            android:layout_alignTop="@+id/apps_list_view"
-            android:id="@+id/fast_scroller_popup"
-            android:layout_alignParentEnd="true"
-            android:layout_marginEnd="@dimen/fastscroll_popup_margin" />
-
         <!-- Note: we are reusing/repurposing a system attribute for search layout, because of a
          platform bug, which prevents using custom attributes in <include> tag -->
         <include
             layout="?android:attr/keyboardLayout"
             android:id="@+id/search_container" />
 
+        <include layout="@layout/all_apps_fast_scroller" />
+
     </com.android.launcher3.allapps.AllAppsRecyclerViewContainerView>
     <View
         style="@style/AllAppsNavBarProtection"
diff --git a/res/layout/all_apps_fast_scroller.xml b/res/layout/all_apps_fast_scroller.xml
new file mode 100644
index 0000000..12c15cc
--- /dev/null
+++ b/res/layout/all_apps_fast_scroller.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<merge
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto">
+    <!-- Fast scroller popup -->
+    <TextView
+        android:id="@+id/fast_scroller_popup"
+        style="@style/FastScrollerPopup"
+        android:layout_alignParentEnd="true"
+        android:layout_alignTop="@+id/apps_list_view"
+        android:layout_marginEnd="@dimen/fastscroll_popup_margin" />
+
+    <com.android.launcher3.views.RecyclerViewFastScroller
+        android:id="@+id/fast_scroller"
+        android:layout_width="@dimen/fastscroll_width"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentEnd="true"
+        android:layout_alignTop="@+id/apps_list_view"
+        android:layout_marginEnd="@dimen/fastscroll_end_margin"
+        launcher:canThumbDetach="true" />
+
+</merge>
\ No newline at end of file
diff --git a/res/layout/all_apps_search_container.xml b/res/layout/all_apps_search_container.xml
index c79360f..2528034 100644
--- a/res/layout/all_apps_search_container.xml
+++ b/res/layout/all_apps_search_container.xml
@@ -20,13 +20,24 @@
     android:layout_height="@dimen/all_apps_search_bar_height"
     android:layout_gravity="center|top"
     android:gravity="center|bottom"
-    android:saveEnabled="false">
+    android:saveEnabled="false"
+    android:paddingLeft="@dimen/dynamic_grid_edge_margin"
+    android:paddingRight="@dimen/dynamic_grid_edge_margin"
+    android:layout_marginBottom="-8dp" >
 
+    <!--
+      Note: The following relation should always be true so that the shadows are aligned properly
+      search_box_input.layout_marginBottom
+            == search_divider.layout_marginBottom
+            == - (search_container.layout_marginBottom)
+            >= 5dp
+    -->
     <com.android.launcher3.ExtendedEditText
         android:id="@+id/search_box_input"
         android:layout_width="match_parent"
         android:layout_height="@dimen/all_apps_search_bar_field_height"
         android:layout_gravity="bottom"
+        android:layout_marginBottom="8dp"
         android:background="@android:color/transparent"
         android:focusableInTouchMode="true"
         android:gravity="center"
@@ -39,4 +50,19 @@
         android:textColor="?android:attr/textColorSecondary"
         android:textColorHint="@drawable/all_apps_search_hint"
         android:textSize="16sp" />
+
+    <!-- This needs to be a container with a view, to simulate a scrolling effect for the header.
+         We translate the header down, and its content up by the same amount, so that it gets
+         clipped by the parent, making it look like the divider was scrolled out of the view. -->
+    <FrameLayout
+        android:id="@+id/search_divider"
+        android:layout_width="match_parent"
+        android:layout_height="1dp"
+        android:layout_gravity="bottom"
+        android:layout_marginBottom="8dp" >
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="1dp"
+            android:background="@drawable/all_apps_search_divider"/>
+    </FrameLayout>
 </com.android.launcher3.allapps.search.AppsSearchContainerLayout>
\ No newline at end of file
diff --git a/res/layout/all_apps_search_divider.xml b/res/layout/all_apps_search_divider.xml
deleted file mode 100644
index c052c66..0000000
--- a/res/layout/all_apps_search_divider.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:importantForAccessibility="no"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:paddingBottom="@dimen/all_apps_divider_margin_vertical"
-    android:paddingLeft="@dimen/dynamic_grid_edge_margin"
-    android:paddingRight="@dimen/dynamic_grid_edge_margin"
-    android:src="@drawable/all_apps_search_divider"
-    android:scaleType="fitXY"
-    android:focusable="false" />
\ No newline at end of file
diff --git a/res/layout/widgets_bottom_sheet.xml b/res/layout/widgets_bottom_sheet.xml
index f1370f0..e8c6961 100644
--- a/res/layout/widgets_bottom_sheet.xml
+++ b/res/layout/widgets_bottom_sheet.xml
@@ -22,7 +22,8 @@
     android:paddingTop="28dp"
     android:background="?android:attr/colorPrimary"
     android:elevation="@dimen/deep_shortcuts_elevation"
-    android:layout_gravity="bottom">
+    android:layout_gravity="bottom"
+    android:theme="?attr/widgetsTheme">
 
     <TextView
         style="@style/TextTitle"
diff --git a/res/layout/widgets_view.xml b/res/layout/widgets_view.xml
index 47b0683..4f3c7c8 100644
--- a/res/layout/widgets_view.xml
+++ b/res/layout/widgets_view.xml
@@ -23,25 +23,25 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:descendantFocusability="afterDescendants"
-    launcher:revealBackground="@drawable/round_rect_primary"
-    android:theme="@style/WidgetContainerTheme">
+    android:theme="?attr/widgetsTheme"
+    launcher:revealBackground="@drawable/round_rect_primary">
 
     <View
         android:id="@+id/reveal_view"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_gravity="center"
-        android:focusable="false"
         android:elevation="2dp"
+        android:focusable="false"
         android:visibility="invisible" />
 
     <FrameLayout
         android:id="@+id/main_content"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
         android:layout_gravity="center"
         android:elevation="15dp"
-        android:visibility="gone"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
+        android:visibility="gone">
 
         <com.android.launcher3.widget.WidgetsRecyclerView
             android:id="@+id/widgets_list_view"
@@ -50,17 +50,24 @@
 
         <!-- Fast scroller popup -->
         <TextView
-            style="@style/FastScrollerPopup"
             android:id="@+id/fast_scroller_popup"
+            style="@style/FastScrollerPopup"
             android:layout_gravity="top|end"
             android:layout_marginEnd="@dimen/fastscroll_popup_margin" />
 
         <ProgressBar
+            android:id="@+id/loader"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:id="@+id/loader"
             android:layout_gravity="center" />
-    </FrameLayout>
 
+        <com.android.launcher3.views.RecyclerViewFastScroller
+            android:id="@+id/fast_scroller"
+            android:layout_width="@dimen/fastscroll_width"
+            android:layout_height="match_parent"
+            android:layout_gravity="end"
+            android:layout_marginEnd="@dimen/fastscroll_end_margin" />
+
+    </FrameLayout>
 
 </com.android.launcher3.widget.WidgetsContainerView>
\ No newline at end of file
diff --git a/res/values-v26/styles.xml b/res/values-v26/styles.xml
index cb18409..fd6fc4d 100644
--- a/res/values-v26/styles.xml
+++ b/res/values-v26/styles.xml
@@ -21,6 +21,10 @@
     <style name="WidgetContainerTheme" parent="@android:style/Theme.DeviceDefault.Settings">
         <item name="android:colorEdgeEffect">?android:attr/textColorSecondary</item>
     </style>
+    <style name="WidgetContainerTheme.Dark" parent="LauncherThemeDark">
+        <item name="android:colorEdgeEffect">?android:attr/textColorSecondary</item>
+        <item name="android:colorPrimaryDark">#616161</item> <!-- Gray 700 -->
+    </style>
 
     <!-- From O and above, we show a dark nav bar in all-apps -->
     <style name="AllAppsNavBarProtection">
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 7929e65..7b52dae 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -30,6 +30,7 @@
     <attr name="workspaceAmbientShadowColor" format="color"/>
     <attr name="workspaceKeyShadowColor" format="color" />
     <attr name="workspaceStatusBarScrim" format="reference" />
+    <attr name="widgetsTheme" format="reference" />
 
     <!-- BubbleTextView specific attributes. -->
     <declare-styleable name="BubbleTextView">
@@ -137,4 +138,8 @@
         <attr name="android:elevation" />
         <attr name="darkTintColor" format="color"/>
     </declare-styleable>
+
+    <declare-styleable name="RecyclerViewFastScroller">
+        <attr name="canThumbDetach" format="boolean" />
+    </declare-styleable>
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 71f9edc..a4dff71 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -65,7 +65,15 @@
     <dimen name="fastscroll_popup_text_size">32dp</dimen>
     <dimen name="fastscroll_popup_margin">19dp</dimen>
 
-<!-- All Apps -->
+    <!--
+      Fast scroller draws the content horizontally centered. The end of the track should be
+      aligned at the end of the container.
+        fastscroll_end_margin = - (fastscroll_width - fastscroll_track_min_width) / 2
+    -->
+    <dimen name="fastscroll_width">58dp</dimen>
+    <dimen name="fastscroll_end_margin">-26dp</dimen>
+
+    <!-- All Apps -->
     <dimen name="all_apps_button_scale_down">0dp</dimen>
     <dimen name="all_apps_search_bar_field_height">48dp</dimen>
     <dimen name="all_apps_search_bar_height">60dp</dimen>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 8943a45..d11b002 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -40,6 +40,7 @@
         <item name="workspaceAmbientShadowColor">#33000000</item>
         <item name="workspaceKeyShadowColor">#44000000</item>
         <item name="workspaceStatusBarScrim">@drawable/workspace_bg</item>
+        <item name="widgetsTheme">@style/WidgetContainerTheme</item>
     </style>
 
     <style name="LauncherTheme" parent="@style/BaseLauncherThemeWithCustomAttrs"></style>
@@ -64,6 +65,7 @@
         <item name="popupColorPrimary">?android:attr/colorPrimary</item>
         <item name="popupColorSecondary">#424242</item> <!-- Gray 800 -->
         <item name="popupColorTertiary">#757575</item> <!-- Gray 600 -->
+        <item name="widgetsTheme">@style/WidgetContainerTheme.Dark</item>
         <item name="isMainColorDark">true</item>
     </style>
 
@@ -85,6 +87,8 @@
         <item name="android:textColorSecondary">?android:attr/textColorSecondaryInverse</item>
     </style>
 
+    <style name="WidgetContainerTheme.Dark" />
+
     <style name="FastScrollerPopup" >
         <item name="android:layout_width">wrap_content</item>
         <item name="android:minWidth">@dimen/fastscroll_popup_width</item>
diff --git a/res/xml/launcher_preferences.xml b/res/xml/launcher_preferences.xml
index 939fece..c76f118 100644
--- a/res/xml/launcher_preferences.xml
+++ b/res/xml/launcher_preferences.xml
@@ -16,23 +16,6 @@
 
 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
 
-    <SwitchPreference
-        android:key="pref_add_icon_to_home"
-        android:title="@string/auto_add_shortcuts_label"
-        android:summary="@string/auto_add_shortcuts_description"
-        android:defaultValue="true"
-        android:persistent="true"
-        />
-
-    <ListPreference
-        android:key="pref_override_icon_shape"
-        android:title="@string/icon_shape_override_label"
-        android:summary="%s"
-        android:entries="@array/icon_shape_override_paths_names"
-        android:entryValues="@array/icon_shape_override_paths_values"
-        android:defaultValue=""
-        android:persistent="false" />
-
     <Preference
         android:key="pref_icon_badging"
         android:title="@string/icon_badging_title"
@@ -46,10 +29,27 @@
     </Preference>
 
     <SwitchPreference
+        android:key="pref_add_icon_to_home"
+        android:title="@string/auto_add_shortcuts_label"
+        android:summary="@string/auto_add_shortcuts_description"
+        android:defaultValue="true"
+        android:persistent="true"
+        />
+
+    <SwitchPreference
         android:key="pref_allowRotation"
         android:title="@string/allow_rotation_title"
         android:defaultValue="@bool/allow_rotation"
         android:persistent="true"
         />
 
+    <ListPreference
+        android:key="pref_override_icon_shape"
+        android:title="@string/icon_shape_override_label"
+        android:summary="%s"
+        android:entries="@array/icon_shape_override_paths_names"
+        android:entryValues="@array/icon_shape_override_paths_values"
+        android:defaultValue=""
+        android:persistent="false" />
+
 </PreferenceScreen>
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 84358ea..3ee6e51 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -22,8 +22,9 @@
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.ViewGroup;
+import android.widget.TextView;
 
-import com.android.launcher3.util.Thunk;
+import com.android.launcher3.views.RecyclerViewFastScroller;
 
 
 /**
@@ -36,19 +37,7 @@
 public abstract class BaseRecyclerView extends RecyclerView
         implements RecyclerView.OnItemTouchListener {
 
-    private static final int SCROLL_DELTA_THRESHOLD_DP = 4;
-
-    /** Keeps the last known scrolling delta/velocity along y-axis. */
-    @Thunk int mDy = 0;
-    private float mDeltaThreshold;
-
-    protected final BaseRecyclerViewFastScrollBar mScrollbar;
-
-    private int mDownX;
-    private int mDownY;
-    private int mLastY;
-
-    private boolean mScrollBarVisible = true;
+    protected RecyclerViewFastScroller mScrollbar;
 
     public BaseRecyclerView(Context context) {
         this(context, null);
@@ -60,28 +49,6 @@
 
     public BaseRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        mDeltaThreshold = getResources().getDisplayMetrics().density * SCROLL_DELTA_THRESHOLD_DP;
-        mScrollbar = new BaseRecyclerViewFastScrollBar(this, getResources());
-
-        ScrollListener listener = new ScrollListener();
-        setOnScrollListener(listener);
-    }
-
-    private class ScrollListener extends OnScrollListener {
-        public ScrollListener() {
-            // Do nothing
-        }
-
-        @Override
-        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
-            mDy = dy;
-
-            // TODO(winsonc): If we want to animate the section heads while scrolling, we can
-            //                initiate that here if the recycler view scroll state is not
-            //                RecyclerView.SCROLL_STATE_IDLE.
-
-            onUpdateScrollbar(dy);
-        }
     }
 
     @Override
@@ -93,7 +60,9 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        mScrollbar.setPopupView(((ViewGroup) getParent()).findViewById(R.id.fast_scroller_popup));
+        ViewGroup parent = (ViewGroup) getParent();
+        mScrollbar = parent.findViewById(R.id.fast_scroller);
+        mScrollbar.setRecyclerView(this, (TextView) parent.findViewById(R.id.fast_scroller_popup));
     }
 
     /**
@@ -115,32 +84,15 @@
      * it is already showing).
      */
     private boolean handleTouchEvent(MotionEvent ev) {
-        ev.offsetLocation(0, -getPaddingTop());
-        int action = ev.getAction();
-        int x = (int) ev.getX();
-        int y = (int) ev.getY();
-        switch (action) {
-            case MotionEvent.ACTION_DOWN:
-                // Keep track of the down positions
-                mDownX = x;
-                mDownY = mLastY = y;
-                if (shouldStopScroll(ev)) {
-                    stopScroll();
-                }
-                mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
-                break;
-            case MotionEvent.ACTION_MOVE:
-                mLastY = y;
-                mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
-                break;
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_CANCEL:
-                onFastScrollCompleted();
-                mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
-                break;
+        // Move to mScrollbar's coordinate system.
+        int left = getLeft() - mScrollbar.getLeft();
+        int top = getTop() - mScrollbar.getTop();
+        ev.offsetLocation(left, top);
+        try {
+            return mScrollbar.handleTouchEvent(ev);
+        } finally {
+            ev.offsetLocation(-left, -top);
         }
-        ev.offsetLocation(0, getPaddingTop());
-        return mScrollbar.isDraggingThumb();
     }
 
     public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
@@ -148,24 +100,9 @@
     }
 
     /**
-     * Returns whether this {@link MotionEvent} should trigger the scroll to be stopped.
-     */
-    protected boolean shouldStopScroll(MotionEvent ev) {
-        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            if ((Math.abs(mDy) < mDeltaThreshold &&
-                    getScrollState() != RecyclerView.SCROLL_STATE_IDLE)) {
-                // now the touch events are being passed to the {@link WidgetCell} until the
-                // touch sequence goes over the touch slop.
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
      * Returns the height of the fast scroll bar
      */
-    protected int getScrollbarTrackHeight() {
+    public int getScrollbarTrackHeight() {
         return getHeight() - getPaddingTop() - getPaddingBottom();
     }
 
@@ -187,25 +124,14 @@
     /**
      * Returns the scrollbar for this recycler view.
      */
-    public BaseRecyclerViewFastScrollBar getScrollBar() {
+    public RecyclerViewFastScroller getScrollBar() {
         return mScrollbar;
     }
 
     @Override
     protected void dispatchDraw(Canvas canvas) {
+        onUpdateScrollbar(0);
         super.dispatchDraw(canvas);
-        if (mScrollBarVisible) {
-            onUpdateScrollbar(0);
-            mScrollbar.draw(canvas);
-        }
-    }
-
-    /**
-     * Sets the scrollbar visibility. The call does not refresh the UI, its the responsibility
-     * of the caller to call {@link #invalidate()}.
-     */
-    public void setScrollBarVisible(boolean visible) {
-        mScrollBarVisible = visible;
     }
 
     /**
@@ -236,7 +162,7 @@
     /**
      * @return whether fast scrolling is supported in the current state.
      */
-    protected boolean supportsFastScrolling() {
+    public boolean supportsFastScrolling() {
         return true;
     }
 
@@ -252,16 +178,16 @@
      * Maps the touch (from 0..1) to the adapter position that should be visible.
      * <p>Override in each subclass of this base class.
      */
-    protected abstract String scrollToPositionAtProgress(float touchFraction);
+    public abstract String scrollToPositionAtProgress(float touchFraction);
 
     /**
      * Updates the bounds for the scrollbar.
      * <p>Override in each subclass of this base class.
      */
-    protected abstract void onUpdateScrollbar(int dy);
+    public abstract void onUpdateScrollbar(int dy);
 
     /**
      * <p>Override in each subclass of this base class.
      */
-    protected void onFastScrollCompleted() {}
+    public void onFastScrollCompleted() {}
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
deleted file mode 100644
index 9e8d300..0000000
--- a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
+++ /dev/null
@@ -1,314 +0,0 @@
-/*
- * 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.animation.ObjectAnimator;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.Rect;
-import android.util.Property;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.widget.TextView;
-
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.graphics.FastScrollThumbDrawable;
-import com.android.launcher3.util.Themes;
-
-/**
- * The track and scrollbar that shows when you scroll the list.
- */
-public class BaseRecyclerViewFastScrollBar {
-
-    private static final Property<BaseRecyclerViewFastScrollBar, Integer> TRACK_WIDTH =
-            new Property<BaseRecyclerViewFastScrollBar, Integer>(Integer.class, "width") {
-
-                @Override
-                public Integer get(BaseRecyclerViewFastScrollBar scrollBar) {
-                    return scrollBar.mWidth;
-                }
-
-                @Override
-                public void set(BaseRecyclerViewFastScrollBar scrollBar, Integer value) {
-                    scrollBar.setTrackWidth(value);
-                }
-            };
-
-    private final static int MAX_TRACK_ALPHA = 30;
-    private final static int SCROLL_BAR_VIS_DURATION = 150;
-    private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 0.75f;
-
-    private final Rect mTmpRect = new Rect();
-    private final BaseRecyclerView mRv;
-
-    private final boolean mIsRtl;
-
-    // The inset is the buffer around which a point will still register as a click on the scrollbar
-    private final int mTouchInset;
-
-    private final int mMinWidth;
-    private final int mMaxWidth;
-    private final int mThumbPadding;
-
-    // Current width of the track
-    private int mWidth;
-    private ObjectAnimator mWidthAnimator;
-
-    private final Paint mThumbPaint;
-    private final int mThumbHeight;
-
-    private final Paint mTrackPaint;
-
-    private float mLastTouchY;
-    private boolean mIsDragging;
-    private boolean mIsThumbDetached;
-    private boolean mCanThumbDetach;
-    private boolean mIgnoreDragGesture;
-
-    // This is the offset from the top of the scrollbar when the user first starts touching.  To
-    // prevent jumping, this offset is applied as the user scrolls.
-    private int mTouchOffsetY;
-    private int mThumbOffsetY;
-
-    // Fast scroller popup
-    private TextView mPopupView;
-    private boolean mPopupVisible;
-    private String mPopupSectionName;
-
-    public BaseRecyclerViewFastScrollBar(BaseRecyclerView rv, Resources res) {
-        mRv = rv;
-        mTrackPaint = new Paint();
-        mTrackPaint.setColor(Themes.getAttrColor(rv.getContext(), android.R.attr.textColorPrimary));
-        mTrackPaint.setAlpha(MAX_TRACK_ALPHA);
-
-        mThumbPaint = new Paint();
-        mThumbPaint.setAntiAlias(true);
-        mThumbPaint.setColor(Themes.getColorAccent(rv.getContext()));
-        mThumbPaint.setStyle(Paint.Style.FILL);
-
-        mWidth = mMinWidth = res.getDimensionPixelSize(R.dimen.fastscroll_track_min_width);
-        mMaxWidth = res.getDimensionPixelSize(R.dimen.fastscroll_track_max_width);
-
-        mThumbPadding = res.getDimensionPixelSize(R.dimen.fastscroll_thumb_padding);
-        mThumbHeight = res.getDimensionPixelSize(R.dimen.fastscroll_thumb_height);
-
-        mTouchInset = res.getDimensionPixelSize(R.dimen.fastscroll_thumb_touch_inset);
-        mIsRtl = Utilities.isRtl(res);
-    }
-
-    public void setPopupView(View popup) {
-        mPopupView = (TextView) popup;
-        mPopupView.setBackground(new FastScrollThumbDrawable(mThumbPaint, mIsRtl));
-    }
-
-    public void setDetachThumbOnFastScroll() {
-        mCanThumbDetach = true;
-    }
-
-    public void reattachThumbToScroll() {
-        mIsThumbDetached = false;
-    }
-
-    private int getDrawLeft() {
-        return mIsRtl ? 0 : (mRv.getWidth() - mMaxWidth);
-    }
-
-    public void setThumbOffsetY(int y) {
-        if (mThumbOffsetY == y) {
-            return;
-        }
-
-        // Invalidate the previous and new thumb area
-        int drawLeft = getDrawLeft();
-        mTmpRect.set(drawLeft, mThumbOffsetY, drawLeft + mMaxWidth, mThumbOffsetY + mThumbHeight);
-        mThumbOffsetY = y;
-        mTmpRect.union(drawLeft, mThumbOffsetY, drawLeft + mMaxWidth, mThumbOffsetY + mThumbHeight);
-        mTmpRect.offset(0, mRv.getPaddingTop());
-        mRv.invalidate(mTmpRect);
-    }
-
-    public int getThumbOffsetY() {
-        return mThumbOffsetY;
-    }
-
-    private void setTrackWidth(int width) {
-        if (mWidth == width) {
-            return;
-        }
-        int left = getDrawLeft();
-        int top = mRv.getPaddingTop();
-        // Invalidate the whole scroll bar area.
-        mRv.invalidate(left, top, left + mMaxWidth, top + mRv.getScrollbarTrackHeight());
-
-        mWidth = width;
-    }
-
-    public int getThumbHeight() {
-        return mThumbHeight;
-    }
-
-    public boolean isDraggingThumb() {
-        return mIsDragging;
-    }
-
-    public boolean isThumbDetached() {
-        return mIsThumbDetached;
-    }
-
-    /**
-     * Handles the touch event and determines whether to show the fast scroller (or updates it if
-     * it is already showing).
-     */
-    public void handleTouchEvent(MotionEvent ev, int downX, int downY, int lastY) {
-        ViewConfiguration config = ViewConfiguration.get(mRv.getContext());
-
-        int action = ev.getAction();
-        int y = (int) ev.getY();
-        switch (action) {
-            case MotionEvent.ACTION_DOWN:
-                if (isNearThumb(downX, downY)) {
-                    mTouchOffsetY = downY - mThumbOffsetY;
-                } else if (FeatureFlags.LAUNCHER3_DIRECT_SCROLL
-                        && mRv.supportsFastScrolling()
-                        && isNearScrollBar(downX)) {
-                    calcTouchOffsetAndPrepToFastScroll(downY, lastY);
-                    updateFastScrollSectionNameAndThumbOffset(lastY, y);
-                }
-                break;
-            case MotionEvent.ACTION_MOVE:
-                // Check if we should start scrolling, but ignore this fastscroll gesture if we have
-                // exceeded some fixed movement
-                mIgnoreDragGesture |= Math.abs(y - downY) > config.getScaledPagingTouchSlop();
-                if (!mIsDragging && !mIgnoreDragGesture && mRv.supportsFastScrolling() &&
-                        isNearThumb(downX, lastY) &&
-                        Math.abs(y - downY) > config.getScaledTouchSlop()) {
-                    calcTouchOffsetAndPrepToFastScroll(downY, lastY);
-                }
-                if (mIsDragging) {
-                    updateFastScrollSectionNameAndThumbOffset(lastY, y);
-                }
-                break;
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_CANCEL:
-                mTouchOffsetY = 0;
-                mLastTouchY = 0;
-                mIgnoreDragGesture = false;
-                if (mIsDragging) {
-                    mIsDragging = false;
-                    animatePopupVisibility(false);
-                    showActiveScrollbar(false);
-                }
-                break;
-        }
-    }
-
-    private void calcTouchOffsetAndPrepToFastScroll(int downY, int lastY) {
-        mRv.getParent().requestDisallowInterceptTouchEvent(true);
-        mIsDragging = true;
-        if (mCanThumbDetach) {
-            mIsThumbDetached = true;
-        }
-        mTouchOffsetY += (lastY - downY);
-        animatePopupVisibility(true);
-        showActiveScrollbar(true);
-    }
-
-    private void updateFastScrollSectionNameAndThumbOffset(int lastY, int y) {
-        // Update the fastscroller section name at this touch position
-        int bottom = mRv.getScrollbarTrackHeight() - mThumbHeight;
-        float boundedY = (float) Math.max(0, Math.min(bottom, y - mTouchOffsetY));
-        String sectionName = mRv.scrollToPositionAtProgress(boundedY / bottom);
-        if (!sectionName.equals(mPopupSectionName)) {
-            mPopupSectionName = sectionName;
-            mPopupView.setText(sectionName);
-        }
-        animatePopupVisibility(!sectionName.isEmpty());
-        updatePopupY(lastY);
-        mLastTouchY = boundedY;
-        setThumbOffsetY((int) mLastTouchY);
-    }
-
-    public void draw(Canvas canvas) {
-        if (mThumbOffsetY < 0) {
-            return;
-        }
-        int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
-        if (!mIsRtl) {
-            canvas.translate(mRv.getWidth() - mWidth, 0);
-        }
-        canvas.translate(0, mRv.getPaddingTop());
-        // Draw the track
-        canvas.drawRoundRect(0, 0, mWidth, mRv.getScrollbarTrackHeight(),
-                mWidth, mWidth, mTrackPaint);
-
-        canvas.translate(-mThumbPadding, mThumbOffsetY);
-        float r = mWidth + mThumbPadding + mThumbPadding;
-        canvas.drawRoundRect(0, 0, r, mThumbHeight, r, r, mThumbPaint);
-        canvas.restoreToCount(saveCount);
-    }
-
-    /**
-     * Animates the width of the scrollbar.
-     */
-    private void showActiveScrollbar(boolean isScrolling) {
-        if (mWidthAnimator != null) {
-            mWidthAnimator.cancel();
-        }
-
-        mWidthAnimator = ObjectAnimator.ofInt(this, TRACK_WIDTH,
-                isScrolling ? mMaxWidth : mMinWidth);
-        mWidthAnimator.setDuration(SCROLL_BAR_VIS_DURATION);
-        mWidthAnimator.start();
-    }
-
-    /**
-     * Returns whether the specified point is inside the thumb bounds.
-     */
-    public boolean isNearThumb(int x, int y) {
-        int left = getDrawLeft();
-        mTmpRect.set(left, mThumbOffsetY, left + mMaxWidth, mThumbOffsetY + mThumbHeight);
-        mTmpRect.inset(mTouchInset, mTouchInset);
-        return mTmpRect.contains(x, y);
-    }
-
-    /**
-     * Returns whether the specified x position is near the scroll bar.
-     */
-    public boolean isNearScrollBar(int x) {
-        int left = getDrawLeft();
-        return x >= left && x <= left + mMaxWidth;
-    }
-
-    private void animatePopupVisibility(boolean visible) {
-        if (mPopupVisible != visible) {
-            mPopupVisible = visible;
-            mPopupView.animate().cancel();
-            mPopupView.animate().alpha(visible ? 1f : 0f).setDuration(visible ? 200 : 150).start();
-        }
-    }
-
-    private void updatePopupY(int lastTouchY) {
-        int height = mPopupView.getHeight();
-        float top = lastTouchY - (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * height);
-        top = Utilities.boundToRange(top,
-                mMaxWidth, mRv.getScrollbarTrackHeight() - mMaxWidth - height);
-        mPopupView.setTranslationY(top);
-    }
-}
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 383e6ef..5e06763 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -41,7 +41,6 @@
 import android.support.annotation.NonNull;
 import android.text.TextUtils;
 import android.util.Log;
-
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
@@ -52,7 +51,6 @@
 import com.android.launcher3.util.Provider;
 import com.android.launcher3.util.SQLiteCacheHelper;
 import com.android.launcher3.util.Thunk;
-
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -91,7 +89,7 @@
 
     private final Context mContext;
     private final PackageManager mPackageManager;
-    private IconProvider mIconProvider;
+    private final IconProvider mIconProvider;
     @Thunk final UserManagerCompat mUserManager;
     private final LauncherAppsCompat mLauncherApps;
     private final HashMap<ComponentKey, CacheEntry> mCache =
@@ -193,7 +191,7 @@
      * Remove any records for the supplied package name from memory.
      */
     private void removeFromMemCacheLocked(String packageName, UserHandle user) {
-        HashSet<ComponentKey> forDeletion = new HashSet<ComponentKey>();
+        HashSet<ComponentKey> forDeletion = new HashSet<>();
         for (ComponentKey key: mCache.keySet()) {
             if (key.componentName.getPackageName().equals(packageName)
                     && key.user.equals(user)) {
@@ -219,7 +217,6 @@
             }
         } catch (NameNotFoundException e) {
             Log.d(TAG, "Package not found", e);
-            return;
         }
     }
 
@@ -264,7 +261,7 @@
             Set<String> ignorePackages) {
         long userSerial = mUserManager.getSerialNumberForUser(user);
         PackageManager pm = mContext.getPackageManager();
-        HashMap<String, PackageInfo> pkgInfoMap = new HashMap<String, PackageInfo>();
+        HashMap<String, PackageInfo> pkgInfoMap = new HashMap<>();
         for (PackageInfo info : pm.getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES)) {
             pkgInfoMap.put(info.packageName, info);
         }
@@ -274,7 +271,7 @@
             componentMap.put(app.getComponentName(), app);
         }
 
-        HashSet<Integer> itemsToRemove = new HashSet<Integer>();
+        HashSet<Integer> itemsToRemove = new HashSet<>();
         Stack<LauncherActivityInfo> appsToUpdate = new Stack<>();
 
         Cursor c = null;
@@ -704,7 +701,7 @@
         private final HashMap<String, PackageInfo> mPkgInfoMap;
         private final Stack<LauncherActivityInfo> mAppsToAdd;
         private final Stack<LauncherActivityInfo> mAppsToUpdate;
-        private final HashSet<String> mUpdatedPackages = new HashSet<String>();
+        private final HashSet<String> mUpdatedPackages = new HashSet<>();
 
         @Thunk SerializedIconUpdateTask(long userSerial, HashMap<String, PackageInfo> pkgInfoMap,
                 Stack<LauncherActivityInfo> appsToAdd,
@@ -753,7 +750,7 @@
     }
 
     private static final class IconDB extends SQLiteCacheHelper {
-        private final static int DB_VERSION = 16;
+        private final static int DB_VERSION = 17;
 
         private final static int RELEASE_VERSION = DB_VERSION +
                 (FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ? 0 : 1);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index d79fcca..26c5c9d 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -79,7 +79,6 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.OvershootInterpolator;
 import android.view.inputmethod.InputMethodManager;
-import android.widget.TextView;
 import android.widget.Toast;
 
 import com.android.launcher3.DropTarget.DragObject;
@@ -2626,9 +2625,9 @@
         if (Utilities.ATLEAST_MARSHMALLOW) {
             int left = 0, top = 0;
             int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
-            if (v instanceof TextView) {
+            if (v instanceof BubbleTextView) {
                 // Launch from center of icon, not entire view
-                Drawable icon = Workspace.getTextViewIcon((TextView) v);
+                Drawable icon = ((BubbleTextView) v).getIcon();
                 if (icon != null) {
                     Rect bounds = icon.getBounds();
                     left = (width - bounds.width()) / 2;
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 255677a..ed225c9 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -24,7 +24,6 @@
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.res.TypedArray;
-import android.graphics.Canvas;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.os.Bundle;
@@ -49,7 +48,6 @@
 
 import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.pageindicators.PageIndicator;
-import com.android.launcher3.util.LauncherEdgeEffect;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.Thunk;
 
@@ -70,6 +68,11 @@
     public static final int PAGE_SNAP_ANIMATION_DURATION = 750;
     protected static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950;
 
+    // Overscroll constants
+    private final static int OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION = 270;
+    private static final float OVERSCROLL_ACCELERATE_FACTOR = 2;
+    private static final float OVERSCROLL_DAMP_FACTOR = 0.07f;
+
     private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f;
     // The page is moved more than halfway, automatically move to the next page on touch up.
     private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.4f;
@@ -145,6 +148,13 @@
 
     protected boolean mWasInOverscroll = false;
 
+    // mOverScrollX is equal to getScrollX() when we're within the normal scroll range. Otherwise
+    // it is equal to the scaled overscroll position. We use a separate value so as to prevent
+    // the screens from continuing to translate beyond the normal bounds.
+    protected int mOverScrollX;
+
+    protected int mUnboundedScrollX;
+
     // Page Indicator
     @Thunk int mPageIndicatorViewId;
     protected PageIndicator mPageIndicator;
@@ -184,10 +194,6 @@
     protected final Rect mInsets = new Rect();
     protected final boolean mIsRtl;
 
-    // Edge effect
-    private final LauncherEdgeEffect mEdgeGlowLeft = new LauncherEdgeEffect();
-    private final LauncherEdgeEffect mEdgeGlowRight = new LauncherEdgeEffect();
-
     public PagedView(Context context) {
         this(context, null);
     }
@@ -229,8 +235,6 @@
         setWillNotDraw(false);
 
         int edgeEffectColor = Themes.getAttrColor(getContext(), android.R.attr.colorEdgeEffect);
-        mEdgeGlowLeft.setColor(edgeEffectColor);
-        mEdgeGlowRight.setColor(edgeEffectColor);
     }
 
     protected void setDefaultInterpolator(Interpolator interpolator) {
@@ -476,7 +480,7 @@
     }
 
     protected int getUnboundedScrollX() {
-        return getScrollX();
+        return mUnboundedScrollX;
     }
 
     @Override
@@ -499,6 +503,8 @@
             x = Math.max(x, mFreeScrollMinScrollX);
         }
 
+        mUnboundedScrollX = x;
+
         boolean isXBeforeFirstPage = mIsRtl ? (x > mMaxScrollX) : (x < 0);
         boolean isXAfterLastPage = mIsRtl ? (x < 0) : (x > mMaxScrollX);
         if (isXBeforeFirstPage) {
@@ -526,6 +532,7 @@
                 overScroll(0);
                 mWasInOverscroll = false;
             }
+            mOverScrollX = x;
             super.scrollTo(x, y);
         }
 
@@ -565,7 +572,8 @@
         if (mScroller.computeScrollOffset()) {
             // Don't bother scrolling if the page does not need to be moved
             if (getUnboundedScrollX() != mScroller.getCurrX()
-                    || getScrollY() != mScroller.getCurrY()) {
+                    || getScrollY() != mScroller.getCurrY()
+                    || mOverScrollX != mScroller.getCurrX()) {
                 float scaleX = mFreeScroll ? getScaleX() : 1f;
                 int scrollX = (int) (mScroller.getCurrX() * (1 / scaleX));
                 scrollTo(scrollX, mScroller.getCurrY());
@@ -973,47 +981,6 @@
     }
 
     @Override
-    public void draw(Canvas canvas) {
-        super.draw(canvas);
-        if (getPageCount() > 0) {
-            if (!mEdgeGlowLeft.isFinished()) {
-                final int restoreCount = canvas.save();
-                Rect display = mViewport;
-                canvas.translate(display.left, display.top);
-                canvas.rotate(270);
-
-                getEdgeVerticalPosition(sTmpIntPoint);
-                canvas.translate(display.top - sTmpIntPoint[1], 0);
-                mEdgeGlowLeft.setSize(sTmpIntPoint[1] - sTmpIntPoint[0], display.width());
-                if (mEdgeGlowLeft.draw(canvas)) {
-                    postInvalidateOnAnimation();
-                }
-                canvas.restoreToCount(restoreCount);
-            }
-            if (!mEdgeGlowRight.isFinished()) {
-                final int restoreCount = canvas.save();
-                Rect display = mViewport;
-                canvas.translate(display.left + mPageScrolls[mIsRtl ? 0 : (getPageCount() - 1)], display.top);
-                canvas.rotate(90);
-
-                getEdgeVerticalPosition(sTmpIntPoint);
-
-                canvas.translate(sTmpIntPoint[0] - display.top, -display.width());
-                mEdgeGlowRight.setSize(sTmpIntPoint[1] - sTmpIntPoint[0], display.width());
-                if (mEdgeGlowRight.draw(canvas)) {
-                    postInvalidateOnAnimation();
-                }
-                canvas.restoreToCount(restoreCount);
-            }
-        }
-    }
-
-    /**
-     * Returns the top and bottom position for the edge effect.
-     */
-    protected abstract void getEdgeVerticalPosition(int[] pos);
-
-    @Override
     public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
         int page = indexToPage(indexOfChild(child));
         if (page != mCurrentPage || !mScroller.isFinished()) {
@@ -1335,6 +1302,29 @@
         }
     }
 
+    // This curve determines how the effect of scrolling over the limits of the page dimishes
+    // as the user pulls further and further from the bounds
+    private float overScrollInfluenceCurve(float f) {
+        f -= 1.0f;
+        return f * f * f + 1.0f;
+    }
+
+    protected float acceleratedOverFactor(float amount) {
+        int screenSize = getViewportWidth();
+
+        // We want to reach the max over scroll effect when the user has
+        // over scrolled half the size of the screen
+        float f = OVERSCROLL_ACCELERATE_FACTOR * (amount / screenSize);
+
+        if (Float.compare(f, 0f) == 0) return 0;
+
+        // Clamp this factor, f, to -1 < f < 1
+        if (Math.abs(f) >= 1) {
+            f /= Math.abs(f);
+        }
+        return f;
+    }
+
     // While layout transitions are occurring, a child's position may stray from its baseline
     // position. This method returns the magnitude of this stray at any given time.
     public int getLayoutTransitionOffsetForPage(int index) {
@@ -1356,13 +1346,25 @@
 
     protected void dampedOverScroll(float amount) {
         int screenSize = getViewportWidth();
+
         float f = (amount / screenSize);
-        if (f < 0) {
-            mEdgeGlowLeft.onPull(-f);
-        } else if (f > 0) {
-            mEdgeGlowRight.onPull(f);
+
+        if (Float.compare(f, 0f) == 0) return;
+
+        f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
+
+        // Clamp this factor, f, to -1 < f < 1
+        if (Math.abs(f) >= 1) {
+            f /= Math.abs(f);
+        }
+
+        int overScrollAmount = (int) Math.round(OVERSCROLL_DAMP_FACTOR * f * screenSize);
+        if (amount < 0) {
+            mOverScrollX = overScrollAmount;
+            super.scrollTo(mOverScrollX, getScrollY());
         } else {
-            return;
+            mOverScrollX = mMaxScrollX + overScrollAmount;
+            super.scrollTo(mOverScrollX, getScrollY());
         }
         invalidate();
     }
@@ -1371,6 +1373,14 @@
         dampedOverScroll(amount);
     }
 
+    protected float maxOverScroll() {
+        // Using the formula in overScroll, assuming that f = 1.0 (which it should generally not
+        // exceed). Used to find out how much extra wallpaper we need for the over scroll effect
+        float f = 1.0f;
+        f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
+        return OVERSCROLL_DAMP_FACTOR * f;
+    }
+
     /**
      * return true if freescroll has been enabled, false otherwise
      */
@@ -1715,8 +1725,6 @@
         mCancelTap = false;
         mTouchState = TOUCH_STATE_REST;
         mActivePointerId = INVALID_POINTER;
-        mEdgeGlowLeft.onRelease();
-        mEdgeGlowRight.onRelease();
     }
 
     /**
@@ -1830,7 +1838,18 @@
     }
 
     protected void snapToDestination() {
-        snapToPage(getPageNearestToCenterOfScreen(), PAGE_SNAP_ANIMATION_DURATION);
+        snapToPage(getPageNearestToCenterOfScreen(), getPageSnapDuration());
+    }
+
+    protected boolean isInOverScroll() {
+        return (mOverScrollX > mMaxScrollX || mOverScrollX < 0);
+    }
+
+    protected int getPageSnapDuration() {
+        if (isInOverScroll()) {
+            return OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION;
+        }
+        return PAGE_SNAP_ANIMATION_DURATION;
     }
 
     public static class ScrollInterpolator implements Interpolator {
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index ad1be7e..a65ea9b 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -111,7 +111,7 @@
      * sizes (landscape vs portrait).
      */
     private static class CacheDb extends SQLiteCacheHelper {
-        private static final int DB_VERSION = 8;
+        private static final int DB_VERSION = 9;
 
         private static final String TABLE_NAME = "shortcut_and_widget_previews";
         private static final String COLUMN_COMPONENT = "componentName";
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 0fabeeb..919c60a 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -51,7 +51,6 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
-import android.widget.TextView;
 import android.widget.Toast;
 import com.android.launcher3.Launcher.CustomContentCallbacks;
 import com.android.launcher3.Launcher.LauncherOverlay;
@@ -301,8 +300,7 @@
     boolean mScrollInteractionBegan;
     boolean mStartedSendingScrollEvents;
     float mLastOverlayScroll = 0;
-    // Total over scrollX in the overlay direction.
-    private int mUnboundedScrollX;
+
     private boolean mForceDrawAdjacentPages = false;
     // Total over scrollX in the overlay direction.
     private float mOverlayTranslation;
@@ -1322,18 +1320,10 @@
         onOverlayScrollChanged(0);
     }
 
-    @Override
-    protected int getUnboundedScrollX() {
-        if (isScrollingOverlay()) {
-            return mUnboundedScrollX;
-        }
-
-        return super.getUnboundedScrollX();
-    }
 
     private boolean isScrollingOverlay() {
         return mLauncherOverlay != null &&
-                ((mIsRtl && mUnboundedScrollX > mMaxScrollX) || (!mIsRtl && mUnboundedScrollX < 0));
+                ((mIsRtl && getUnboundedScrollX() > mMaxScrollX) || (!mIsRtl && getUnboundedScrollX() < 0));
     }
 
     @Override
@@ -1353,12 +1343,6 @@
     }
 
     @Override
-    public void scrollTo(int x, int y) {
-        mUnboundedScrollX = x;
-        super.scrollTo(x, y);
-    }
-
-    @Override
     protected void onScrollChanged(int l, int t, int oldl, int oldt) {
         super.onScrollChanged(l, t, oldl, oldt);
 
@@ -1531,13 +1515,6 @@
     }
 
     @Override
-    protected void getEdgeVerticalPosition(int[] pos) {
-        View child = getChildAt(getPageCount() - 1);
-        pos[0] = child.getTop();
-        pos[1] = child.getBottom();
-    }
-
-    @Override
     protected void notifyPageSwitchListener() {
         super.notifyPageSwitchListener();
 
@@ -2083,19 +2060,6 @@
         }
     }
 
-    /**
-     * Returns the drawable for the given text view.
-     */
-    public static Drawable getTextViewIcon(TextView tv) {
-        final Drawable[] drawables = tv.getCompoundDrawables();
-        for (int i = 0; i < drawables.length; i++) {
-            if (drawables[i] != null) {
-                return drawables[i];
-            }
-        }
-        return null;
-    }
-
     public void startDrag(CellLayout.CellInfo cellInfo, DragOptions options) {
         View child = cellInfo.cell;
 
@@ -3865,7 +3829,7 @@
                         updates.contains(info)) {
                     ShortcutInfo si = (ShortcutInfo) info;
                     BubbleTextView shortcut = (BubbleTextView) v;
-                    Drawable oldIcon = getTextViewIcon(shortcut);
+                    Drawable oldIcon = shortcut.getIcon();
                     boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable)
                             && ((PreloadIconDrawable) oldIcon).hasNotCompleted();
                     shortcut.applyFromShortcutInfo(si, si.isPromise() != oldPromiseState);
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 4954e0c..189b935 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -171,19 +171,19 @@
      * Returns whether the view itself will handle the touch event or not.
      */
     public boolean shouldContainerScroll(MotionEvent ev) {
-        int[] point = new int[2];
-        point[0] = (int) ev.getX();
-        point[1] = (int) ev.getY();
-        Utilities.mapCoordInSelfToDescendant(mAppsRecyclerView, this, point);
-
         // IF the MotionEvent is inside the search box, and the container keeps on receiving
         // touch input, container should move down.
         if (mLauncher.getDragLayer().isEventOverView(mSearchContainer, ev)) {
             return true;
         }
 
+        int[] point = new int[2];
+        point[0] = (int) ev.getX();
+        point[1] = (int) ev.getY();
+        Utilities.mapCoordInSelfToDescendant(
+                mAppsRecyclerView.getScrollBar(), mLauncher.getDragLayer(), point);
         // IF the MotionEvent is inside the thumb, container should not be pulled down.
-        if (mAppsRecyclerView.getScrollBar().isNearThumb(point[0], point[1])) {
+        if (mAppsRecyclerView.getScrollBar().shouldBlockIntercept(point[0], point[1])) {
             return false;
         }
 
@@ -220,7 +220,7 @@
         });
 
         // Load the all apps recycler view
-        mAppsRecyclerView = (AllAppsRecyclerView) findViewById(R.id.apps_list_view);
+        mAppsRecyclerView = findViewById(R.id.apps_list_view);
         mAppsRecyclerView.setApps(mApps);
         mAppsRecyclerView.setLayoutManager(mLayoutManager);
         mAppsRecyclerView.setAdapter(mAdapter);
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index d6514a8..ba4fbe0 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -69,16 +69,13 @@
 
     // A divider that separates the apps list and the search market button
     public static final int VIEW_TYPE_SEARCH_MARKET_DIVIDER = 1 << 5;
-    // The divider under the search field
-    public static final int VIEW_TYPE_SEARCH_DIVIDER = 1 << 6;
     // The divider that separates prediction icons from the app list
-    public static final int VIEW_TYPE_PREDICTION_DIVIDER = 1 << 7;
-    public static final int VIEW_TYPE_APPS_LOADING_DIVIDER = 1 << 8;
-    public static final int VIEW_TYPE_DISCOVERY_ITEM = 1 << 9;
+    public static final int VIEW_TYPE_PREDICTION_DIVIDER = 1 << 6;
+    public static final int VIEW_TYPE_APPS_LOADING_DIVIDER = 1 << 7;
+    public static final int VIEW_TYPE_DISCOVERY_ITEM = 1 << 8;
 
     // Common view type masks
-    public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_SEARCH_DIVIDER
-            | VIEW_TYPE_SEARCH_MARKET_DIVIDER
+    public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_SEARCH_MARKET_DIVIDER
             | VIEW_TYPE_PREDICTION_DIVIDER;
     public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON
             | VIEW_TYPE_PREDICTION_ICON;
@@ -319,9 +316,6 @@
                     }
                 });
                 return new ViewHolder(searchMarketView);
-            case VIEW_TYPE_SEARCH_DIVIDER:
-                return new ViewHolder(mLayoutInflater.inflate(
-                        R.layout.all_apps_search_divider, parent, false));
             case VIEW_TYPE_APPS_LOADING_DIVIDER:
                 View loadingDividerView = mLayoutInflater.inflate(
                         R.layout.all_apps_discovery_loading_divider, parent, false);
@@ -487,19 +481,17 @@
          *     5 6 7 8 9
          */
         private int getAppPosition(int position, int numPredictedApps, int appsPerRow) {
-            int appPosition = position;
-            int numDividerViews = 1 + (numPredictedApps == 0 ? 0 : 1);
-
-            int allAppsStartAt = numDividerViews + numPredictedApps;
-            if (numDividerViews == 1 || position < allAppsStartAt) {
-                appPosition -= 1;
-            } else {
-                // We cannot assume that the predicted row will always be full.
-                int numPredictedAppsOffset = appsPerRow - numPredictedApps;
-                appPosition = position + numPredictedAppsOffset - numDividerViews;
+            if (position < numPredictedApps) {
+                // Predicted apps are first in the adapter.
+                return position;
             }
 
-            return appPosition;
+            // There is at most 1 divider view between the predicted apps and the alphabetical apps.
+            int numDividerViews = numPredictedApps == 0 ? 0 : 1;
+
+            // This offset takes into consideration an incomplete row of predicted apps.
+            int numPredictedAppsOffset = appsPerRow - numPredictedApps;
+            return position + numPredictedAppsOffset - numDividerViews;
         }
 
         /**
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 0607a1e..a2bd43d 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -15,12 +15,14 @@
  */
 package com.android.launcher3.allapps;
 
+import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
 import android.support.v7.widget.RecyclerView;
 import android.util.AttributeSet;
+import android.util.Property;
 import android.util.SparseIntArray;
 import android.view.MotionEvent;
 import android.view.View;
@@ -54,6 +56,22 @@
     private int mEmptySearchBackgroundTopOffset;
 
     private SpringAnimationHandler mSpringAnimationHandler;
+    private OverScrollHelper mOverScrollHelper;
+    private VerticalPullDetector mPullDetector;
+
+    private float mContentTranslationY = 0;
+    public static final Property<AllAppsRecyclerView, Float> CONTENT_TRANS_Y =
+            new Property<AllAppsRecyclerView, Float>(Float.class, "appsRecyclerViewContentTransY") {
+                @Override
+                public Float get(AllAppsRecyclerView allAppsRecyclerView) {
+                    return allAppsRecyclerView.getContentTranslationY();
+                }
+
+                @Override
+                public void set(AllAppsRecyclerView allAppsRecyclerView, Float y) {
+                    allAppsRecyclerView.setContentTranslationY(y);
+                }
+            };
 
     public AllAppsRecyclerView(Context context) {
         this(context, null);
@@ -72,9 +90,14 @@
         super(context, attrs, defStyleAttr);
         Resources res = getResources();
         addOnItemTouchListener(this);
-        mScrollbar.setDetachThumbOnFastScroll();
         mEmptySearchBackgroundTopOffset = res.getDimensionPixelSize(
                 R.dimen.all_apps_empty_search_bg_top_offset);
+
+        mOverScrollHelper = new OverScrollHelper();
+        mPullDetector = new VerticalPullDetector(getContext());
+        mPullDetector.setListener(mOverScrollHelper);
+        mPullDetector.setDetectableScrollConditions(VerticalPullDetector.DIRECTION_UP
+                | VerticalPullDetector.DIRECTION_DOWN, true);
     }
 
     public void setSpringAnimationHandler(SpringAnimationHandler springAnimationHandler) {
@@ -82,7 +105,14 @@
     }
 
     @Override
+    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) {
+        mPullDetector.onTouchEvent(ev);
+        return super.onInterceptTouchEvent(rv, ev) || mOverScrollHelper.isInOverScroll();
+    }
+
+    @Override
     public boolean onTouchEvent(MotionEvent e) {
+        mPullDetector.onTouchEvent(e);
         if (FeatureFlags.LAUNCHER3_PHYSICS && mSpringAnimationHandler != null) {
             mSpringAnimationHandler.addMovement(e);
         }
@@ -110,7 +140,6 @@
         RecyclerView.RecycledViewPool pool = getRecycledViewPool();
         int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx);
         pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH, 1);
-        pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_SEARCH_DIVIDER, 1);
         pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET_DIVIDER, 1);
         pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET, 1);
         pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_ICON, approxRows * mNumAppsPerRow);
@@ -137,8 +166,6 @@
                 AllAppsGridAdapter.VIEW_TYPE_PREDICTION_DIVIDER,
                 AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET_DIVIDER);
         putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
-                AllAppsGridAdapter.VIEW_TYPE_SEARCH_DIVIDER);
-        putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
                 AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET);
         putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
                 AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH);
@@ -164,12 +191,16 @@
      */
     public void scrollToTop() {
         // Ensure we reattach the scrollbar if it was previously detached while fast-scrolling
-        mScrollbar.reattachThumbToScroll();
+        if (mScrollbar != null) {
+            mScrollbar.reattachThumbToScroll();
+        }
         scrollToPosition(0);
     }
 
     @Override
     public void onDraw(Canvas c) {
+        c.translate(0, mContentTranslationY);
+
         // Draw the background
         if (mEmptySearchBackground != null && mEmptySearchBackground.getAlpha() > 0) {
             mEmptySearchBackground.draw(c);
@@ -178,6 +209,19 @@
         super.onDraw(c);
     }
 
+    public float getContentTranslationY() {
+        return mContentTranslationY;
+    }
+
+    /**
+     * Use this method instead of calling {@link #setTranslationY(float)}} directly to avoid drawing
+     * on top of other Views.
+     */
+    public void setContentTranslationY(float y) {
+        mContentTranslationY = y;
+        invalidate();
+    }
+
     @Override
     protected boolean verifyDrawable(Drawable who) {
         return who == mEmptySearchBackground || super.verifyDrawable(who);
@@ -356,7 +400,7 @@
     }
 
     @Override
-    protected boolean supportsFastScrolling() {
+    public boolean supportsFastScrolling() {
         // Only allow fast scrolling when the user is not searching, since the results are not
         // grouped in a meaningful order
         return !mApps.hasFilter();
@@ -436,4 +480,84 @@
                 y + mEmptySearchBackground.getIntrinsicHeight());
     }
 
+    private class OverScrollHelper implements VerticalPullDetector.Listener {
+
+        private static final float MAX_RELEASE_VELOCITY = 5000; // px / s
+        private static final float MAX_OVERSCROLL_PERCENTAGE = 0.07f;
+
+        private boolean mIsInOverScroll;
+
+        @Override
+        public void onDragStart(boolean start) {
+        }
+
+        @Override
+        public boolean onDrag(float displacement, float velocity) {
+            // We are in overscroll iff we are trying to drag further down when we're already at
+            // the bottom of All Apps.
+            mIsInOverScroll = !canScrollVertically(1) && displacement < 0;
+
+            if (mIsInOverScroll) {
+                displacement = getDampedOverScroll(displacement);
+                setContentTranslationY(displacement);
+            }
+            return mIsInOverScroll;
+        }
+
+        @Override
+        public void onDragEnd(float velocity, boolean fling) {
+            float y = getContentTranslationY();
+            if (mIsInOverScroll && Float.compare(y, 0) != 0) {
+                if (FeatureFlags.LAUNCHER3_PHYSICS) {
+                    // We calculate our own velocity to give the springs the desired effect.
+                    velocity = y / getDampedOverScroll(getHeight()) * MAX_RELEASE_VELOCITY;
+                    mSpringAnimationHandler.animateToPositionWithVelocity(0, -velocity);
+                }
+
+                ObjectAnimator.ofFloat(AllAppsRecyclerView.this,
+                        AllAppsRecyclerView.CONTENT_TRANS_Y, 0)
+                        .setDuration(100)
+                        .start();
+            }
+            mIsInOverScroll = false;
+        }
+
+        public boolean isInOverScroll() {
+            return mIsInOverScroll;
+        }
+
+        private float getDampedOverScroll(float y) {
+            return dampedOverScroll(y, getHeight()) * MAX_OVERSCROLL_PERCENTAGE;
+        }
+
+        /**
+         * This curve determines how the effect of scrolling over the limits of the page diminishes
+         * as the user pulls further and further from the bounds
+         *
+         * @param f The percentage of how much the user has overscrolled.
+         * @return A transformed percentage based on the influence curve.
+         */
+        private float overScrollInfluenceCurve(float f) {
+            f -= 1.0f;
+            return f * f * f + 1.0f;
+        }
+
+        /**
+         * @param amount The original amount overscrolled.
+         * @param max The maximum amount that the View can overscroll.
+         * @return The dampened overscroll amount.
+         */
+        private float dampedOverScroll(float amount, float max) {
+            float f = amount / max;
+            if (Float.compare(f, 0) == 0) return 0;
+            f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
+
+            // Clamp this factor, f, to -1 < f < 1
+            if (Math.abs(f) >= 1) {
+                f /= Math.abs(f);
+            }
+
+            return Math.round(f * max);
+        }
+    }
 }
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 7bf6651..608e898 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -138,13 +138,6 @@
             return item;
         }
 
-        public static AdapterItem asSearchDivider(int pos) {
-            AdapterItem item = new AdapterItem();
-            item.viewType = AllAppsGridAdapter.VIEW_TYPE_SEARCH_DIVIDER;
-            item.position = pos;
-            return item;
-        }
-
         public static AdapterItem asMarketDivider(int pos) {
             AdapterItem item = new AdapterItem();
             item.viewType = AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET_DIVIDER;
@@ -195,8 +188,6 @@
     private int mNumPredictedAppsPerRow;
     private int mNumAppRowsInAdapter;
 
-    private boolean mHasSearchDivider = true;
-
     public AlphabeticalAppsList(Context context) {
         mLauncher = Launcher.getLauncher(context);
         mIndexer = new AlphabeticIndexCompat(context);
@@ -352,10 +343,6 @@
         onAppsUpdated();
     }
 
-    public void disableSearchDivider() {
-        mHasSearchDivider = false;
-    }
-
     /**
      * Updates internals when the set of apps are updated.
      */
@@ -442,11 +429,6 @@
             }
         }
 
-        if (mHasSearchDivider) {
-            // Add the search divider
-            mAdapterItems.add(AdapterItem.asSearchDivider(position++));
-        }
-
         // Process the predicted app components
         mPredictedApps.clear();
         if (mPredictedAppComponents != null && !mPredictedAppComponents.isEmpty() && !hasFilter()) {
diff --git a/src/com/android/launcher3/allapps/LandscapeFastScroller.java b/src/com/android/launcher3/allapps/LandscapeFastScroller.java
new file mode 100644
index 0000000..cdde657
--- /dev/null
+++ b/src/com/android/launcher3/allapps/LandscapeFastScroller.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.allapps;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+import com.android.launcher3.views.RecyclerViewFastScroller;
+
+/**
+ * Extension of {@link RecyclerViewFastScroller} to be used in landscape layout.
+ */
+public class LandscapeFastScroller extends RecyclerViewFastScroller {
+
+    public LandscapeFastScroller(Context context) {
+        super(context);
+    }
+
+    public LandscapeFastScroller(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public LandscapeFastScroller(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    public boolean handleTouchEvent(MotionEvent ev) {
+        // We handle our own touch event, no need to handle recycler view touch delegates.
+        return false;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        event.offsetLocation(0, -mRv.getPaddingTop());
+        if (super.handleTouchEvent(event)) {
+            getParent().requestDisallowInterceptTouchEvent(true);
+        }
+        event.offsetLocation(0, mRv.getPaddingTop());
+        return true;
+    }
+
+    @Override
+    public boolean shouldBlockIntercept(int x, int y) {
+        // If the user touched the scroll bar area, block swipe
+        return x >= 0 && x < getWidth() && y >= 0 && y < getHeight();
+    }
+}
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index 3f06ec9..5cb12d5 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -54,12 +54,13 @@
     private final int mSearchBoxHeight;
     private final AllAppsSearchBarController mSearchBarController;
     private final SpannableStringBuilder mSearchQueryBuilder;
-    private final HeaderElevationController mElevationController;
 
     private ExtendedEditText mSearchInput;
     private AlphabeticalAppsList mApps;
     private AllAppsRecyclerView mAppsRecyclerView;
     private AllAppsGridAdapter mAdapter;
+    private View mDivider;
+    private HeaderElevationController mElevationController;
 
     public AppsSearchContainerLayout(Context context) {
         this(context, null);
@@ -77,7 +78,6 @@
         mSearchBoxHeight = getResources()
                 .getDimensionPixelSize(R.dimen.all_apps_search_bar_field_height);
         mSearchBarController = new AllAppsSearchBarController();
-        mElevationController = new HeaderElevationController(this);
 
         mSearchQueryBuilder = new SpannableStringBuilder();
         Selection.setSelection(mSearchQueryBuilder, 0);
@@ -87,6 +87,8 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mSearchInput = findViewById(R.id.search_box_input);
+        mDivider = findViewById(R.id.search_divider);
+        mElevationController = new HeaderElevationController(mDivider);
 
         // Update the hint to contain the icon.
         // Prefix the original hint with two spaces. The first space gets replaced by the icon
@@ -96,6 +98,12 @@
         spanned.setSpan(new TintedDrawableSpan(getContext(), R.drawable.ic_allapps_search),
                 0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
         mSearchInput.setHint(spanned);
+
+        DeviceProfile dp = mLauncher.getDeviceProfile();
+        if (!dp.isVerticalBarLayout()) {
+            LayoutParams lp = (LayoutParams) mDivider.getLayoutParams();
+            lp.leftMargin = lp.rightMargin = dp.edgeMarginPx;
+        }
     }
 
     @Override
diff --git a/src/com/android/launcher3/allapps/search/HeaderElevationController.java b/src/com/android/launcher3/allapps/search/HeaderElevationController.java
index ab4e88f..7cd32b2 100644
--- a/src/com/android/launcher3/allapps/search/HeaderElevationController.java
+++ b/src/com/android/launcher3/allapps/search/HeaderElevationController.java
@@ -4,11 +4,11 @@
 import android.graphics.Outline;
 import android.support.v7.widget.RecyclerView;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.ViewOutlineProvider;
 
 import com.android.launcher3.BaseRecyclerView;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 
 /**
  * Helper class for controlling the header elevation in response to RecyclerView scroll.
@@ -16,6 +16,7 @@
 public class HeaderElevationController extends RecyclerView.OnScrollListener {
 
     private final View mHeader;
+    private final View mHeaderChild;
     private final float mMaxElevation;
     private final float mScrollToElevation;
 
@@ -28,23 +29,27 @@
         mScrollToElevation = res.getDimension(R.dimen.all_apps_header_scroll_to_elevation);
 
         // We need to provide a custom outline so the shadow only appears on the bottom edge.
-        // The top, left and right edges are all extended out, and the shadow is clipped
-        // by the parent.
+        // The top, left and right edges are all extended out to match parent's edge, so that
+        // the shadow is clipped by the parent.
         final ViewOutlineProvider vop = new ViewOutlineProvider() {
             @Override
             public void getOutline(View view, Outline outline) {
-                final View parent = (View) mHeader.getParent();
+                // Set the left and top to be at the parents edge. Since the coordinates are
+                // relative to this view,
+                //    (x = -view.getLeft()) for this view => (x = 0) for parent
+                final int left = -view.getLeft();
+                final int top = -view.getTop();
 
-                final int left = parent.getLeft(); // Use the parent to account for offsets
-                final int top = view.getTop();
-                final int right = left + view.getWidth();
-                final int bottom = view.getBottom();
-
-                final int offset = Utilities.pxFromDp(mMaxElevation, res.getDisplayMetrics());
+                // Since the view is centered align, the spacing on left and right are same.
+                // Add same spacing on the right to reach parent's edge.
+                final int right = view.getWidth() - left;
+                final int bottom = view.getHeight();
+                final int offset = (int) mMaxElevation;
                 outline.setRect(left - offset, top - offset, right + offset, bottom);
             }
         };
         mHeader.setOutlineProvider(vop);
+        mHeaderChild = ((ViewGroup) mHeader).getChildAt(0);
     }
 
     public void reset() {
@@ -63,6 +68,13 @@
         float newElevation = mMaxElevation * elevationPct;
         if (Float.compare(mHeader.getElevation(), newElevation) != 0) {
             mHeader.setElevation(newElevation);
+
+            // To simulate a scrolling effect for the header, we translate the header down, and
+            // its content up by the same amount, so that it gets clipped by the parent, making it
+            // look like the content was scrolled out of the view.
+            int shift = Math.min(mHeader.getHeight(), scrollY);
+            mHeader.setTranslationY(-shift);
+            mHeaderChild.setTranslationY(shift);
         }
     }
 
diff --git a/src/com/android/launcher3/anim/SpringAnimationHandler.java b/src/com/android/launcher3/anim/SpringAnimationHandler.java
index 038f826..1efc4e4 100644
--- a/src/com/android/launcher3/anim/SpringAnimationHandler.java
+++ b/src/com/android/launcher3/anim/SpringAnimationHandler.java
@@ -127,6 +127,19 @@
         reset();
     }
 
+    /**
+     * Similar to {@link #animateToFinalPosition(float)}, but used in cases where we want to
+     * manually set the velocity.
+     */
+    public void animateToPositionWithVelocity(float position, float velocity) {
+        if (DEBUG) Log.d(TAG, "animateToPosition#velocity=" + velocity);
+
+        setStartVelocity(velocity);
+        mShouldComputeVelocity = false;
+        animateToFinalPosition(position);
+    }
+
+
     public boolean isRunning() {
         // All the animations run at the same time so we can just check the first one.
         return !mAnimations.isEmpty() && mAnimations.get(0).isRunning();
@@ -153,6 +166,8 @@
     }
 
     private void setStartVelocity(float velocity) {
+        if (DEBUG) Log.d(TAG, "setStartVelocity=" + velocity);
+
         int size = mAnimations.size();
         for (int i = 0; i < size; ++i) {
             mAnimations.get(i).setStartVelocity(velocity);
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 50ad0ff..b852714 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -210,13 +210,13 @@
     }
 
     private void callOnDragStart() {
-        for (DragListener listener : new ArrayList<>(mListeners)) {
-            listener.onDragStart(mDragObject, mOptions);
-        }
         if (mOptions.preDragCondition != null) {
             mOptions.preDragCondition.onPreDragEnd(mDragObject, true /* dragStarted*/);
         }
         mIsInPreDrag = false;
+        for (DragListener listener : new ArrayList<>(mListeners)) {
+            listener.onDragStart(mDragObject, mOptions);
+        }
     }
 
     /**
diff --git a/src/com/android/launcher3/dynamicui/ColorExtractionAlgorithm.java b/src/com/android/launcher3/dynamicui/ColorExtractionAlgorithm.java
index d2dd0d3..de614f0 100644
--- a/src/com/android/launcher3/dynamicui/ColorExtractionAlgorithm.java
+++ b/src/com/android/launcher3/dynamicui/ColorExtractionAlgorithm.java
@@ -29,6 +29,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.WallpaperColorsCompat;
 
+import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.List;
 
@@ -97,7 +98,7 @@
         hsl[0] /= 360f;
 
         // Find the palette that contains the closest color
-        TonalPalette palette = findTonalPalette(hsl[0]);
+        TonalPalette palette = findTonalPalette(hsl[0], hsl[1]);
         if (palette == null) {
             Log.w(TAG, "Could not find a tonal palette!");
             return null;
@@ -116,10 +117,22 @@
         float[] s = fit(palette.s, hsl[1], fitIndex, 0.0f, 1.0f);
         float[] l = fit(palette.l, hsl[2], fitIndex, 0.0f, 1.0f);
 
-        // Normal colors:
-        // best fit + a 2 colors offset
-        int primaryIndex = fitIndex;
-        int secondaryIndex = primaryIndex + (primaryIndex >= 2 ? -2 : 2);
+        final int textInversionIndex = h.length - 3;
+
+        int primaryIndex;
+        int secondaryIndex;
+
+        // Dark colors:
+        // Stops at 4th color, only lighter if dark text is supported
+        if (fitIndex < 2) {
+            primaryIndex = 0;
+        } else if (fitIndex < textInversionIndex) {
+            primaryIndex = Math.min(fitIndex, 3);
+        } else {
+            primaryIndex = h.length - 1;
+        }
+        secondaryIndex = primaryIndex + (primaryIndex >= 2 ? -2 : 2);
+
         int mainColor = getColorInt(primaryIndex, h, s, l);
         int secondaryColor = getColorInt(secondaryIndex, h, s, l);
 
@@ -196,7 +209,13 @@
     }
 
     @Nullable
-    private static TonalPalette findTonalPalette(float h) {
+    private static TonalPalette findTonalPalette(float h, float s) {
+        // Fallback to a grey palette if the color is too desaturated.
+        // This avoids hue shifts.
+        if (s < 0.05f) {
+            return GREY_PALETTE;
+        }
+
         TonalPalette best = null;
         float error = Float.POSITIVE_INFINITY;
 
@@ -250,6 +269,12 @@
         final float maxHue;
 
         TonalPalette(float[] h, float[] s, float[] l) {
+            if (h.length != s.length || s.length != l.length) {
+                throw new IllegalArgumentException("All arrays should have the same size. h: "
+                        + Arrays.toString(h) + " s: " + Arrays.toString(s) + " l: "
+                        + Arrays.toString(l));
+            }
+
             this.h = h;
             this.s = s;
             this.l = l;
@@ -272,271 +297,280 @@
     // a best fit. Each palette is defined as 22 HSL colors
     private static final TonalPalette[] TONAL_PALETTES = {
             new TonalPalette(
-                    new float[]{0.991f, 0.9833333333333333f, 0f, 0f, 0f, 0.01134380453752181f,
-                            0.015625000000000003f, 0.024193548387096798f, 0.027397260273972573f,
-                            0.017543859649122865f},
-                    new float[]{1f, 1f, 1f, 1f, 0.8434782608695652f, 1f, 1f, 1f, 1f, 1f},
-                    new float[]{0.2f, 0.27450980392156865f, 0.34901960784313724f,
-                            0.4235294117647059f, 0.5490196078431373f, 0.6254901960784314f,
-                            0.6862745098039216f, 0.7568627450980392f, 0.8568627450980393f,
-                            0.9254901960784314f}
+                    new float[] {1f, 1f, 0.991f, 0.991f, 0.9833333333333333f, 0f, 0f, 0f,
+                            0.01134380453752181f, 0.015625000000000003f, 0.024193548387096798f,
+                            0.027397260273972573f, 0.017543859649122865f},
+                    new float[] {1f, 1f, 1f, 1f, 1f, 1f, 1f, 0.8434782608695652f, 1f, 1f, 1f, 1f,
+                            1f},
+                    new float[] {0.04f, 0.09f, 0.14f, 0.2f, 0.27450980392156865f,
+                            0.34901960784313724f, 0.4235294117647059f, 0.5490196078431373f,
+                            0.6254901960784314f, 0.6862745098039216f, 0.7568627450980392f,
+                            0.8568627450980393f, 0.9254901960784314f}
             ),
             new TonalPalette(
-                    new float[]{0.6385767790262171f, 0.6301169590643275f, 0.6223958333333334f,
-                            0.6151079136690647f, 0.6065400843881856f, 0.5986964618249534f,
-                            0.5910746812386157f, 0.5833333333333334f, 0.5748031496062993f,
-                            0.5582010582010583f},
-                    new float[]{1f, 1f, 0.9014084507042253f, 0.8128654970760234f,
-                            0.7979797979797981f, 0.7816593886462883f, 0.778723404255319f,
-                            1f, 1f, 1f},
-                    new float[]{0.17450980392156862f, 0.2235294117647059f, 0.2784313725490196f,
-                            0.3352941176470588f, 0.388235294117647f, 0.44901960784313727f,
-                            0.5392156862745098f, 0.6509803921568628f, 0.7509803921568627f,
-                            0.8764705882352941f}
+                    new float[] {0.638f, 0.638f, 0.6385767790262171f, 0.6301169590643275f,
+                            0.6223958333333334f, 0.6151079136690647f, 0.6065400843881856f,
+                            0.5986964618249534f, 0.5910746812386157f, 0.5833333333333334f,
+                            0.5748031496062993f, 0.5582010582010583f},
+                    new float[] {1f, 1f, 1f, 1f, 0.9014084507042253f, 0.8128654970760234f,
+                            0.7979797979797981f, 0.7816593886462883f, 0.778723404255319f, 1f, 1f,
+                            1f},
+                    new float[] {0.05f, 0.12f, 0.17450980392156862f, 0.2235294117647059f,
+                            0.2784313725490196f, 0.3352941176470588f, 0.388235294117647f,
+                            0.44901960784313727f, 0.5392156862745098f, 0.6509803921568628f,
+                            0.7509803921568627f, 0.8764705882352941f}
             ),
             new TonalPalette(
-                    new float[]{0.5669934640522876f, 0.5748031496062993f,
+                    new float[] {0.563f, 0.569f, 0.5666f, 0.5669934640522876f, 0.5748031496062993f,
                             0.5595238095238095f, 0.5473118279569893f, 0.5393258426966292f,
                             0.5315955766192734f, 0.524031007751938f, 0.5154711673699016f,
                             0.508080808080808f, 0.5f},
-                    new float[]{1f, 1f, 1f, 1f, 1f, 1f, 0.8847736625514403f, 1f, 1f, 1f},
-                    new float[]{0.2f, 0.24901960784313726f, 0.27450980392156865f,
-                            0.30392156862745096f, 0.34901960784313724f, 0.4137254901960784f,
-                            0.47647058823529415f, 0.5352941176470588f, 0.6764705882352942f, 0.8f}
+                    new float[] {1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 0.8847736625514403f, 1f, 1f,
+                            1f},
+                    new float[] {0.07f, 0.12f, 0.16f, 0.2f, 0.24901960784313726f,
+                            0.27450980392156865f, 0.30392156862745096f, 0.34901960784313724f,
+                            0.4137254901960784f, 0.47647058823529415f, 0.5352941176470588f,
+                            0.6764705882352942f, 0.8f}
             ),
             new TonalPalette(
-                    new float[]{0.5082304526748972f, 0.5069444444444444f, 0.5f, 0.5f,
-                            0.5f, 0.48724954462659376f, 0.4800347222222222f,
-                            0.4755134281200632f, 0.4724409448818897f, 0.4671052631578947f},
-                    new float[]{1f, 0.8888888888888887f, 0.9242424242424242f, 1f, 1f,
-                            0.8133333333333332f, 0.7868852459016393f, 1f, 1f, 1f},
-                    new float[]{0.1588235294117647f, 0.21176470588235297f,
-                            0.25882352941176473f, 0.3f, 0.34901960784313724f,
+                    new float[] {0.508f, 0.511f, 0.508f, 0.508f, 0.5082304526748972f,
+                            0.5069444444444444f, 0.5f, 0.5f, 0.5f, 0.48724954462659376f,
+                            0.4800347222222222f, 0.4755134281200632f, 0.4724409448818897f,
+                            0.4671052631578947f},
+                    new float[] {1f, 1f, 1f, 1f, 1f, 0.8888888888888887f, 0.9242424242424242f, 1f,
+                            1f, 0.8133333333333332f, 0.7868852459016393f, 1f, 1f, 1f},
+                    new float[] {0.04f, 0.06f, 0.08f, 0.12f, 0.1588235294117647f,
+                            0.21176470588235297f, 0.25882352941176473f, 0.3f, 0.34901960784313724f,
                             0.44117647058823534f, 0.5215686274509804f, 0.5862745098039216f,
                             0.7509803921568627f, 0.8509803921568627f}
             ),
             new TonalPalette(
-                    new float[]{0.3333333333333333f, 0.3333333333333333f,
+                    new float[] {0.333f, 0.333f, 0.333f, 0.3333333333333333f, 0.3333333333333333f,
                             0.34006734006734f, 0.34006734006734f, 0.34006734006734f,
                             0.34259259259259256f, 0.3475783475783476f, 0.34767025089605735f,
                             0.3467741935483871f, 0.3703703703703704f},
-                    new float[]{0.6703296703296703f, 0.728813559322034f,
+                    new float[] {0.70f, 0.72f, 0.69f, 0.6703296703296703f, 0.728813559322034f,
                             0.5657142857142856f, 0.5076923076923077f, 0.3944223107569721f,
                             0.6206896551724138f, 0.8931297709923666f, 1f, 1f, 1f},
-                    new float[]{0.1784313725490196f, 0.23137254901960785f,
+                    new float[] {0.05f, 0.08f, 0.14f, 0.1784313725490196f, 0.23137254901960785f,
                             0.3431372549019608f, 0.38235294117647056f, 0.49215686274509807f,
                             0.6588235294117647f, 0.7431372549019608f, 0.8176470588235294f,
                             0.8784313725490196f, 0.9294117647058824f}
             ),
             new TonalPalette(
-                    new float[]{0.162280701754386f, 0.15032679738562088f,
+                    new float[] {0.161f, 0.163f, 0.163f, 0.162280701754386f, 0.15032679738562088f,
                             0.15879265091863518f, 0.16236559139784948f, 0.17443868739205526f,
-                            0.17824074074074076f, 0.18674698795180725f,
-                            0.18692449355432778f, 0.1946778711484594f, 0.18604651162790695f},
-                    new float[]{1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f},
-                    new float[]{0.14901960784313725f, 0.2f, 0.24901960784313726f,
-                            0.30392156862745096f, 0.3784313725490196f, 0.4235294117647059f,
-                            0.48823529411764705f, 0.6450980392156863f, 0.7666666666666666f,
-                            0.8313725490196078f}
+                            0.17824074074074076f, 0.18674698795180725f, 0.18692449355432778f,
+                            0.1946778711484594f, 0.18604651162790695f},
+                    new float[] {1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f},
+                    new float[] {0.05f, 0.08f, 0.11f, 0.14901960784313725f, 0.2f,
+                            0.24901960784313726f, 0.30392156862745096f, 0.3784313725490196f,
+                            0.4235294117647059f, 0.48823529411764705f, 0.6450980392156863f,
+                            0.7666666666666666f, 0.8313725490196078f}
             ),
             new TonalPalette(
-                    new float[]{0.10619469026548674f, 0.11924686192468618f,
-                            0.13046448087431692f, 0.14248366013071895f, 0.1506024096385542f,
-                            0.16220238095238093f, 0.16666666666666666f,
+                    new float[] {0.108f, 0.105f, 0.105f, 0.105f, 0.10619469026548674f,
+                            0.11924686192468618f, 0.13046448087431692f, 0.14248366013071895f,
+                            0.1506024096385542f, 0.16220238095238093f, 0.16666666666666666f,
                             0.16666666666666666f, 0.162280701754386f, 0.15686274509803924f},
-                    new float[]{1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f},
-                    new float[]{0.44313725490196076f, 0.46862745098039216f,
-                            0.47843137254901963f, 0.5f, 0.5117647058823529f,
+                    new float[] {1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f},
+                    new float[] {0.17f, 0.22f, 0.28f, 0.35f, 0.44313725490196076f,
+                            0.46862745098039216f, 0.47843137254901963f, 0.5f, 0.5117647058823529f,
                             0.5607843137254902f, 0.6509803921568628f, 0.7509803921568627f,
                             0.8509803921568627f, 0.9f}
             ),
             new TonalPalette(
-                    new float[]{0.03561253561253561f, 0.05098039215686275f,
-                            0.07516339869281045f, 0.09477124183006536f, 0.1150326797385621f,
-                            0.134640522875817f, 0.14640522875816991f, 0.1582397003745319f,
-                            0.15773809523809523f, 0.15359477124183002f},
-                    new float[]{1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f},
-                    new float[]{0.4588235294117647f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f,
-                            0.5f, 0.6509803921568628f, 0.7803921568627451f, 0.9f}
+                    new float[] {0.036f, 0.036f, 0.036f, 0.036f, 0.03561253561253561f,
+                            0.05098039215686275f, 0.07516339869281045f, 0.09477124183006536f,
+                            0.1150326797385621f, 0.134640522875817f, 0.14640522875816991f,
+                            0.1582397003745319f, 0.15773809523809523f, 0.15359477124183002f},
+                    new float[] {1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f},
+                    new float[] {0.19f, 0.26f, 0.34f, 0.39f, 0.4588235294117647f, 0.5f, 0.5f, 0.5f,
+                            0.5f, 0.5f, 0.5f, 0.6509803921568628f, 0.7803921568627451f, 0.9f}
             ),
             new TonalPalette(
-                    new float[]{0.9596491228070175f, 0.9593837535014005f,
+                    new float[] {0.955f, 0.961f, 0.958f, 0.9596491228070175f, 0.9593837535014005f,
                             0.9514767932489452f, 0.943859649122807f, 0.9396825396825397f,
                             0.9395424836601307f, 0.9393939393939394f, 0.9362745098039216f,
                             0.9754098360655739f, 0.9824561403508771f},
-                    new float[]{0.84070796460177f, 0.8206896551724138f,
+                    new float[] {0.87f, 0.85f, 0.85f, 0.84070796460177f, 0.8206896551724138f,
                             0.7979797979797981f, 0.7661290322580644f, 0.9051724137931036f,
                             1f, 1f, 1f, 1f, 1f},
-                    new float[]{0.22156862745098038f, 0.2843137254901961f,
+                    new float[] {0.06f, 0.11f, 0.16f, 0.22156862745098038f, 0.2843137254901961f,
                             0.388235294117647f, 0.48627450980392156f, 0.5450980392156863f,
                             0.6f, 0.6764705882352942f, 0.8f, 0.8803921568627451f,
                             0.9254901960784314f}
             ),
             new TonalPalette(
-                    new float[]{0.841025641025641f, 0.8333333333333334f,
+                    new float[] {0.866f, 0.855f, 0.841025641025641f, 0.8333333333333334f,
                             0.8285256410256411f, 0.821522309711286f, 0.8083333333333333f,
                             0.8046594982078853f, 0.8005822416302766f, 0.7842377260981912f,
                             0.7771084337349398f, 0.7747747747747749f},
-                    new float[]{1f, 1f, 1f, 1f, 1f, 1f, 1f,
+                    new float[] {1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f,
                             0.737142857142857f, 0.6434108527131781f, 0.46835443037974644f},
-                    new float[]{0.12745098039215685f, 0.15490196078431373f,
+                    new float[] {0.05f, 0.08f, 0.12745098039215685f, 0.15490196078431373f,
                             0.20392156862745098f, 0.24901960784313726f, 0.3137254901960784f,
-                            0.36470588235294116f, 0.44901960784313727f,
-                            0.6568627450980392f, 0.7470588235294118f, 0.8450980392156863f}
+                            0.36470588235294116f, 0.44901960784313727f, 0.6568627450980392f,
+                            0.7470588235294118f, 0.8450980392156863f}
             ),
             new TonalPalette(
-                    new float[]{0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f},
-                    new float[]{0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f},
-                    new float[]{0.14901960784313725f, 0.2f, 0.2980392156862745f, 0.4f,
-                            0.4980392156862745f, 0.6196078431372549f, 0.7176470588235294f,
-                            0.8196078431372549f, 0.9176470588235294f, 0.9490196078431372f}
-            ),
-            new TonalPalette(
-                    new float[]{0.955952380952381f, 0.9681069958847737f,
-                            0.9760479041916167f, 0.9873563218390804f, 0f, 0f,
+                    new float[] {0.925f, 0.93f, 0.938f, 0.947f, 0.955952380952381f,
+                            0.9681069958847737f, 0.9760479041916167f, 0.9873563218390804f, 0f, 0f,
                             0.009057971014492771f, 0.026748971193415648f,
                             0.041666666666666616f, 0.05303030303030304f},
-                    new float[]{1f, 0.8350515463917526f, 0.6929460580912863f,
+                    new float[] {1f, 1f, 1f, 1f, 1f, 0.8350515463917526f, 0.6929460580912863f,
                             0.6387665198237885f, 0.6914893617021276f, 0.7583892617449666f,
                             0.8070175438596495f, 0.9310344827586209f, 1f, 1f},
-                    new float[]{0.27450980392156865f, 0.3803921568627451f,
-                            0.4725490196078432f, 0.5549019607843138f, 0.6313725490196078f,
-                            0.707843137254902f, 0.7764705882352941f, 0.8294117647058823f,
-                            0.9058823529411765f, 0.9568627450980391f}
+                    new float[] {0.10f, 0.13f, 0.17f, 0.2f, 0.27450980392156865f,
+                            0.3803921568627451f, 0.4725490196078432f, 0.5549019607843138f,
+                            0.6313725490196078f, 0.707843137254902f, 0.7764705882352941f,
+                            0.8294117647058823f, 0.9058823529411765f, 0.9568627450980391f}
             ),
             new TonalPalette(
-                    new float[]{0.7514619883040936f, 0.7679738562091503f,
+                    new float[] {0.733f, 0.736f, 0.744f, 0.7514619883040936f, 0.7679738562091503f,
                             0.7802083333333333f, 0.7844311377245509f, 0.796875f,
                             0.8165618448637316f, 0.8487179487179487f, 0.8582375478927203f,
                             0.8562091503267975f, 0.8666666666666667f},
-                    new float[]{1f, 1f, 0.8163265306122449f, 0.6653386454183268f,
+                    new float[] {1f, 1f, 1f, 1f, 1f, 0.8163265306122449f, 0.6653386454183268f,
                             0.7547169811320753f, 0.929824561403509f, 0.9558823529411766f,
                             0.9560439560439562f, 1f, 1f},
-                    new float[]{0.2235294117647059f, 0.3f, 0.38431372549019605f,
-                            0.492156862745098f, 0.5843137254901961f, 0.6647058823529411f,
-                            0.7333333333333334f, 0.8215686274509804f, 0.9f,
+                    new float[] {0.07f, 0.12f, 0.17f, 0.2235294117647059f, 0.3f,
+                            0.38431372549019605f, 0.492156862745098f, 0.5843137254901961f,
+                            0.6647058823529411f, 0.7333333333333334f, 0.8215686274509804f, 0.9f,
                             0.9411764705882353f}
             ),
             new TonalPalette(
-                    new float[]{0.6666666666666666f, 0.6666666666666666f,
+                    new float[] {0.6666666666666666f, 0.6666666666666666f, 0.6666666666666666f,
                             0.6666666666666666f, 0.6666666666666666f, 0.6666666666666666f,
                             0.6666666666666666f, 0.6666666666666666f, 0.6666666666666666f,
                             0.6666666666666666f, 0.6666666666666666f},
-                    new float[]{0.24590163934426232f, 0.17880794701986752f,
+                    new float[] {0.25f, 0.24590163934426232f, 0.17880794701986752f,
                             0.14606741573033713f, 0.13761467889908252f, 0.14893617021276592f,
-                            0.16756756756756758f, 0.20312500000000017f,
-                            0.26086956521739135f, 0.29999999999999966f, 0.5000000000000004f},
-                    new float[]{0.2392156862745098f, 0.296078431372549f,
+                            0.16756756756756758f, 0.20312500000000017f, 0.26086956521739135f,
+                            0.29999999999999966f, 0.5000000000000004f},
+                    new float[] {0.18f, 0.2392156862745098f, 0.296078431372549f,
                             0.34901960784313724f, 0.4274509803921569f, 0.5392156862745098f,
                             0.6372549019607843f, 0.7490196078431373f, 0.8196078431372549f,
                             0.8823529411764706f, 0.9372549019607843f}
             ),
             new TonalPalette(
-                    new float[]{0.9678571428571429f, 0.9944812362030905f, 0f, 0f,
+                    new float[] {0.938f, 0.944f, 0.952f, 0.961f, 0.9678571428571429f,
+                            0.9944812362030905f, 0f, 0f,
                             0.0047348484848484815f, 0.00316455696202532f, 0f,
                             0.9980392156862745f, 0.9814814814814816f, 0.9722222222222221f},
-                    new float[]{1f, 0.7023255813953488f, 0.6638655462184874f,
+                    new float[] {1f, 1f, 1f, 1f, 1f, 0.7023255813953488f, 0.6638655462184874f,
                             0.6521739130434782f, 0.7719298245614035f, 0.8315789473684211f,
                             0.6867469879518071f, 0.7264957264957265f, 0.8181818181818182f,
                             0.8181818181818189f},
-                    new float[]{0.27450980392156865f, 0.4215686274509804f,
+                    new float[] {0.08f, 0.13f, 0.18f, 0.23f, 0.27450980392156865f,
+                            0.4215686274509804f,
                             0.4666666666666667f, 0.503921568627451f, 0.5529411764705883f,
                             0.6274509803921569f, 0.6745098039215687f, 0.7705882352941176f,
                             0.892156862745098f, 0.9568627450980391f}
             ),
             new TonalPalette(
-                    new float[]{0.9052287581699346f, 0.9112021857923498f, 0.9270152505446624f,
-                            0.9343137254901961f, 0.9391534391534391f, 0.9437984496124031f,
-                            0.943661971830986f, 0.9438943894389439f, 0.9426229508196722f,
-                            0.9444444444444444f},
-                    new float[]{1f, 0.8133333333333332f, 0.7927461139896375f, 0.7798165137614679f,
-                            0.7777777777777779f, 0.8190476190476191f, 0.8255813953488372f,
-                            0.8211382113821142f, 0.8133333333333336f, 0.8000000000000006f},
-                    new float[]{0.2f, 0.29411764705882354f, 0.3784313725490196f,
-                            0.42745098039215684f, 0.4764705882352941f, 0.5882352941176471f,
-                            0.6627450980392157f, 0.7588235294117647f, 0.8529411764705882f,
-                            0.9411764705882353f}
+                    new float[] {0.88f, 0.888f, 0.897f, 0.9052287581699346f, 0.9112021857923498f,
+                            0.9270152505446624f, 0.9343137254901961f, 0.9391534391534391f,
+                            0.9437984496124031f, 0.943661971830986f, 0.9438943894389439f,
+                            0.9426229508196722f, 0.9444444444444444f},
+                    new float[] {1f, 1f, 1f, 1f, 0.8133333333333332f, 0.7927461139896375f,
+                            0.7798165137614679f, 0.7777777777777779f, 0.8190476190476191f,
+                            0.8255813953488372f, 0.8211382113821142f, 0.8133333333333336f,
+                            0.8000000000000006f},
+                    new float[] {0.08f, 0.12f, 0.16f, 0.2f, 0.29411764705882354f,
+                            0.3784313725490196f, 0.42745098039215684f, 0.4764705882352941f,
+                            0.5882352941176471f, 0.6627450980392157f, 0.7588235294117647f,
+                            0.8529411764705882f, 0.9411764705882353f}
             ),
             new TonalPalette(
-                    new float[]{0.6884057971014492f, 0.6974789915966387f, 0.7079889807162534f,
-                            0.7154471544715447f, 0.7217741935483872f, 0.7274143302180687f,
-                            0.7272727272727273f, 0.7258064516129031f, 0.7252252252252251f,
-                            0.7333333333333333f},
-                    new float[]{0.8214285714285715f, 0.6878612716763006f, 0.6080402010050251f,
-                            0.5774647887323943f, 0.5391304347826086f, 0.46724890829694316f,
-                            0.4680851063829788f, 0.462686567164179f, 0.45679012345678977f,
-                            0.4545454545454551f},
-                    new float[]{0.2196078431372549f, 0.33921568627450976f, 0.39019607843137255f,
-                            0.4176470588235294f, 0.45098039215686275f,
+                    new float[] {0.669f, 0.680f, 0.6884057971014492f, 0.6974789915966387f,
+                            0.7079889807162534f, 0.7154471544715447f, 0.7217741935483872f,
+                            0.7274143302180687f, 0.7272727272727273f, 0.7258064516129031f,
+                            0.7252252252252251f, 0.7333333333333333f},
+                    new float[] {0.81f, 0.81f, 0.8214285714285715f, 0.6878612716763006f,
+                            0.6080402010050251f, 0.5774647887323943f, 0.5391304347826086f,
+                            0.46724890829694316f, 0.4680851063829788f, 0.462686567164179f,
+                            0.45679012345678977f, 0.4545454545454551f},
+                    new float[] {0.12f, 0.16f, 0.2196078431372549f, 0.33921568627450976f,
+                            0.39019607843137255f, 0.4176470588235294f, 0.45098039215686275f,
                             0.5509803921568628f, 0.6313725490196078f, 0.7372549019607844f,
                             0.8411764705882353f, 0.9352941176470588f}
             ),
             new TonalPalette(
-                    new float[]{0.6470588235294118f, 0.6516666666666667f, 0.6464174454828661f,
+                    new float[] {0.6470588235294118f, 0.6516666666666667f, 0.6464174454828661f,
                             0.6441441441441442f, 0.6432748538011696f, 0.6416666666666667f,
                             0.6402439024390243f, 0.6412429378531074f, 0.6435185185185186f,
                             0.6428571428571429f},
-                    new float[]{0.8095238095238095f, 0.6578947368421053f, 0.5721925133689839f,
+                    new float[] {0.8095238095238095f, 0.6578947368421053f, 0.5721925133689839f,
                             0.5362318840579711f, 0.5f, 0.4424778761061947f, 0.44086021505376327f,
-                            0.44360902255639095f,
-                            0.4499999999999997f, 0.4375000000000006f},
-                    new float[]{0.16470588235294117f, 0.2980392156862745f, 0.36666666666666664f,
+                            0.44360902255639095f, 0.4499999999999997f, 0.4375000000000006f},
+                    new float[] {0.16470588235294117f, 0.2980392156862745f, 0.36666666666666664f,
                             0.40588235294117647f, 0.44705882352941173f,
                             0.5568627450980392f, 0.6352941176470588f, 0.7392156862745098f,
                             0.8431372549019608f, 0.9372549019607843f}
             ),
             new TonalPalette(
-                    new float[]{0.46732026143790845f, 0.4718614718614719f, 0.4793650793650794f,
-                            0.48071625344352614f, 0.4829683698296837f, 0.484375f,
-                            0.4841269841269842f, 0.48444444444444457f, 0.48518518518518516f,
-                            0.4907407407407408f},
-                    new float[]{1f, 1f, 1f, 1f, 1f, 0.6274509803921569f, 0.41832669322709176f,
-                            0.41899441340782106f, 0.4128440366972478f,
-                            0.4090909090909088f},
-                    new float[]{0.1f, 0.15098039215686274f, 0.20588235294117646f,
+                    new float[] {0.469f, 0.46732026143790845f, 0.4718614718614719f,
+                            0.4793650793650794f, 0.48071625344352614f, 0.4829683698296837f,
+                            0.484375f, 0.4841269841269842f, 0.48444444444444457f,
+                            0.48518518518518516f, 0.4907407407407408f},
+                    new float[] {1f, 1f, 1f, 1f, 1f, 1f, 0.6274509803921569f, 0.41832669322709176f,
+                            0.41899441340782106f, 0.4128440366972478f, 0.4090909090909088f},
+                    new float[] {0.07f, 0.1f, 0.15098039215686274f, 0.20588235294117646f,
                             0.2372549019607843f, 0.26862745098039215f, 0.4f, 0.5078431372549019f,
                             0.6490196078431372f, 0.7862745098039216f, 0.9137254901960784f}
             ),
             new TonalPalette(
-                    new float[]{0.5444444444444444f, 0.5555555555555556f, 0.5555555555555556f,
-                            0.553763440860215f, 0.5526315789473684f, 0.5555555555555556f,
-                            0.5555555555555555f, 0.5555555555555556f, 0.5512820512820514f,
-                            0.5666666666666667f},
-                    new float[]{0.24590163934426232f, 0.19148936170212766f, 0.1791044776119403f,
-                            0.18343195266272191f, 0.18446601941747576f,
+                    new float[] {0.542f, 0.5444444444444444f, 0.5555555555555556f,
+                            0.5555555555555556f, 0.553763440860215f, 0.5526315789473684f,
+                            0.5555555555555556f, 0.5555555555555555f, 0.5555555555555556f,
+                            0.5512820512820514f, 0.5666666666666667f},
+                    new float[] {0.25f, 0.24590163934426232f, 0.19148936170212766f,
+                            0.1791044776119403f, 0.18343195266272191f, 0.18446601941747576f,
                             0.1538461538461539f, 0.15625000000000003f, 0.15328467153284678f,
                             0.15662650602409653f, 0.151515151515151f},
-                    new float[]{0.1196078431372549f, 0.1843137254901961f, 0.2627450980392157f,
+                    new float[] {0.05f, 0.1196078431372549f, 0.1843137254901961f,
+                            0.2627450980392157f,
                             0.33137254901960783f, 0.403921568627451f, 0.5411764705882354f,
                             0.6235294117647059f, 0.7313725490196079f, 0.8372549019607843f,
                             0.9352941176470588f}
             ),
             new TonalPalette(
-                    new float[]{0.022222222222222223f, 0.02469135802469136f, 0.031249999999999997f,
+                    new float[] {0.022222222222222223f, 0.02469135802469136f, 0.031249999999999997f,
                             0.03947368421052631f, 0.04166666666666668f,
                             0.043650793650793655f, 0.04411764705882352f, 0.04166666666666652f,
                             0.04444444444444459f, 0.05555555555555529f},
-                    new float[]{0.33333333333333337f, 0.2783505154639175f, 0.2580645161290323f,
+                    new float[] {0.33333333333333337f, 0.2783505154639175f, 0.2580645161290323f,
                             0.25675675675675674f, 0.2528735632183908f, 0.17500000000000002f,
                             0.15315315315315312f, 0.15189873417721522f,
                             0.15789473684210534f, 0.15789473684210542f},
-                    new float[]{0.08823529411764705f, 0.19019607843137254f, 0.2431372549019608f,
+                    new float[] {0.08823529411764705f, 0.19019607843137254f, 0.2431372549019608f,
                             0.2901960784313725f, 0.3411764705882353f, 0.47058823529411764f,
                             0.5647058823529412f, 0.6901960784313725f, 0.8137254901960784f,
                             0.9254901960784314f}
             ),
             new TonalPalette(
-                    new float[]{0.050884955752212385f, 0.07254901960784313f, 0.0934640522875817f,
+                    new float[] {0.027f, 0.03f, 0.038f, 0.044f, 0.050884955752212385f,
+                            0.07254901960784313f, 0.0934640522875817f,
                             0.10457516339869281f, 0.11699346405228758f,
                             0.1255813953488372f, 0.1268939393939394f, 0.12533333333333332f,
                             0.12500000000000003f, 0.12777777777777777f},
-                    new float[]{1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f},
-                    new float[]{0.44313725490196076f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5784313725490196f,
+                    new float[] {1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f},
+                    new float[] {0.25f, 0.3f, 0.35f, 0.4f, 0.44313725490196076f, 0.5f, 0.5f, 0.5f,
+                            0.5f, 0.5784313725490196f,
                             0.6549019607843137f, 0.7549019607843137f, 0.8509803921568627f,
                             0.9411764705882353f}
             )
     };
 
+    private static final TonalPalette GREY_PALETTE = new TonalPalette(
+            new float[]{0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f},
+            new float[]{0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f},
+            new float[]{0.08f, 0.11f, 0.14901960784313725f, 0.2f, 0.2980392156862745f, 0.4f,
+                    0.4980392156862745f, 0.6196078431372549f, 0.7176470588235294f,
+                    0.8196078431372549f, 0.9176470588235294f, 0.9490196078431372f}
+    );
+
     @SuppressWarnings("WeakerAccess")
     static final ColorRange[] BLACKLISTED_COLORS = new ColorRange[] {
 
@@ -738,5 +772,4 @@
         }
         return colors;
     }
-
 }
diff --git a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
index 33bf275..ff357c0 100644
--- a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
+++ b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
@@ -29,8 +29,8 @@
     }
 
     @Override
-    public FolderIcon.PreviewItemDrawingParams computePreviewItemDrawingParams(int index,
-            int curNumItems, FolderIcon.PreviewItemDrawingParams params) {
+    public PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
+            PreviewItemDrawingParams params) {
 
         float totalScale = scaleForItem(index, curNumItems);
         float transX;
@@ -47,7 +47,7 @@
         }
 
         if (params == null) {
-            params = new FolderIcon.PreviewItemDrawingParams(transX, transY, totalScale, overlayAlpha);
+            params = new PreviewItemDrawingParams(transX, transY, totalScale, overlayAlpha);
         } else {
             params.update(transX, transY, totalScale);
             params.overlayAlpha = overlayAlpha;
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index aad8123..3c7c698 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -791,6 +791,7 @@
         if (mFolderIcon != null) {
             mFolderIcon.setVisibility(View.VISIBLE);
             if (FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION) {
+                mFolderIcon.mFolderName.setTextVisibility(true);
                 mFolderIcon.setBackgroundVisible(true);
                 mFolderIcon.mBackground.fadeInBackgroundShadow();
             }
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index 7e6205a..3648c60 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -71,8 +71,7 @@
     private final TimeInterpolator mFolderInterpolator;
     private final TimeInterpolator mLargeFolderPreviewItemInterpolator;
 
-    private final FolderIcon.PreviewItemDrawingParams mTmpParams =
-            new FolderIcon.PreviewItemDrawingParams(0, 0, 0, 0);
+    private final PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0);
 
     private static final Property<View, Float> SCALE_PROPERTY =
             new Property<View, Float>(Float.class, "scale") {
@@ -202,20 +201,20 @@
         play(a, new RoundedRectRevealOutlineProvider(initialRadius, finalRadius, startRect,
                 endRect).createRevealAnimator(mFolder, !mIsOpening));
 
+        // Animate the elevation midway so that the shadow is not noticeable in the background.
+        int midDuration = mDuration / 2;
+        Animator z = getAnimator(mFolder, View.TRANSLATION_Z, -mFolder.getElevation(), 0);
+        play(a, z, midDuration, midDuration);
+
         a.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
                 super.onAnimationEnd(animation);
                 mFolder.setTranslationX(0.0f);
                 mFolder.setTranslationY(0.0f);
+                mFolder.setTranslationZ(0.0f);
                 mFolder.setScaleX(1f);
                 mFolder.setScaleY(1f);
-
-                if (mIsOpening) {
-                    getAnimator(mFolder, View.TRANSLATION_Z, -mFolder.getElevation(), 0)
-                            .setDuration(150)
-                            .start();
-                }
             }
         });
 
@@ -324,7 +323,12 @@
     }
 
     private void play(AnimatorSet as, Animator a) {
-        a.setDuration(mDuration);
+        play(as, a, a.getStartDelay(), mDuration);
+    }
+
+    private void play(AnimatorSet as, Animator a, long startDelay, int duration) {
+        a.setStartDelay(startDelay);
+        a.setDuration(duration);
         as.play(a);
     }
 
@@ -344,12 +348,6 @@
                 : ObjectAnimator.ofFloat(view, property, v2, v1);
     }
 
-    private Animator getAnimator(List<BubbleTextView> items, Property property, int v1, int v2) {
-        return mIsOpening
-                ? ObjectAnimator.ofArgb(items, property, v1, v2)
-                : ObjectAnimator.ofArgb(items, property, v2, v1);
-    }
-
     private Animator getAnimator(GradientDrawable drawable, String property, int v1, int v2) {
         return mIsOpening
                 ? ObjectAnimator.ofArgb(drawable, property, v1, v2)
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 215a31c..1cc285e 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -19,8 +19,6 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Point;
@@ -227,8 +225,7 @@
     }
 
     public boolean acceptDrop(ItemInfo dragInfo) {
-        final ItemInfo item = dragInfo;
-        return !mFolder.isDestroyed() && willAcceptItem(item);
+        return !mFolder.isDestroyed() && willAcceptItem(dragInfo);
     }
 
     public void addItem(ShortcutInfo item) {
@@ -423,39 +420,6 @@
         return mBadgeInfo != null && mBadgeInfo.hasBadge();
     }
 
-    static class PreviewItemDrawingParams {
-        PreviewItemDrawingParams(float transX, float transY, float scale, float overlayAlpha) {
-            this.transX = transX;
-            this.transY = transY;
-            this.scale = scale;
-            this.overlayAlpha = overlayAlpha;
-        }
-
-        public void update(float transX, float transY, float scale) {
-            // We ensure the update will not interfere with an animation on the layout params
-            // If the final values differ, we cancel the animation.
-            if (anim != null) {
-                if (anim.finalTransX == transX || anim.finalTransY == transY
-                        || anim.finalScale == scale) {
-                    return;
-                }
-                anim.cancel();
-            }
-
-            this.transX = transX;
-            this.transY = transY;
-            this.scale = scale;
-        }
-
-        float transX;
-        float transY;
-        float scale;
-        public float overlayAlpha;
-        boolean hidden;
-        FolderPreviewItemAnim anim;
-        Drawable drawable;
-    }
-
     private float getLocalCenterForIndex(int index, int curNumItems, int[] center) {
         mTmpParams = computePreviewItemDrawingParams(
                 Math.min(mPreviewLayoutRule.maxNumItems(), index), curNumItems, mTmpParams);
@@ -465,12 +429,12 @@
         float offsetX = mTmpParams.transX + (mTmpParams.scale * mIntrinsicIconSize) / 2;
         float offsetY = mTmpParams.transY + (mTmpParams.scale * mIntrinsicIconSize) / 2;
 
-        center[0] = (int) Math.round(offsetX);
-        center[1] = (int) Math.round(offsetY);
+        center[0] = Math.round(offsetX);
+        center[1] = Math.round(offsetY);
         return mTmpParams.scale;
     }
 
-    private PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
+    PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
             PreviewItemDrawingParams params) {
         // We use an index of -1 to represent an icon on the workspace for the destroy and
         // create animations
@@ -582,89 +546,14 @@
         }
     }
 
-    class FolderPreviewItemAnim {
-        ValueAnimator mValueAnimator;
-        float finalScale;
-        float finalTransX;
-        float finalTransY;
-
-        /**
-         *
-         * @param params layout params to animate
-         * @param index0 original index of the item to be animated
-         * @param nItems0 original number of items in the preview
-         * @param index1 new index of the item to be animated
-         * @param nItems1 new number of items in the preview
-         * @param duration duration in ms of the animation
-         * @param onCompleteRunnable runnable to execute upon animation completion
-         */
-        public FolderPreviewItemAnim(final PreviewItemDrawingParams params, int index0, int nItems0,
-                int index1, int nItems1, int duration, final Runnable onCompleteRunnable) {
-
-            computePreviewItemDrawingParams(index1, nItems1, mTmpParams);
-
-            finalScale = mTmpParams.scale;
-            finalTransX = mTmpParams.transX;
-            finalTransY = mTmpParams.transY;
-
-            computePreviewItemDrawingParams(index0, nItems0, mTmpParams);
-
-            final float scale0 = mTmpParams.scale;
-            final float transX0 = mTmpParams.transX;
-            final float transY0 = mTmpParams.transY;
-
-            mValueAnimator = LauncherAnimUtils.ofFloat(0f, 1.0f);
-            mValueAnimator.addUpdateListener(new AnimatorUpdateListener(){
-                public void onAnimationUpdate(ValueAnimator animation) {
-                    float progress = animation.getAnimatedFraction();
-
-                    params.transX = transX0 + progress * (finalTransX - transX0);
-                    params.transY = transY0 + progress * (finalTransY - transY0);
-                    params.scale = scale0 + progress * (finalScale - scale0);
-                    invalidate();
-                }
-            });
-
-            mValueAnimator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationStart(Animator animation) {
-                }
-
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    if (onCompleteRunnable != null) {
-                        onCompleteRunnable.run();
-                    }
-                    params.anim = null;
-                }
-            });
-            mValueAnimator.setDuration(duration);
-        }
-
-        public void start() {
-            mValueAnimator.start();
-        }
-
-        public void cancel() {
-            mValueAnimator.cancel();
-        }
-
-        public boolean hasEqualFinalState(FolderPreviewItemAnim anim) {
-            return finalTransY == anim.finalTransY && finalTransX == anim.finalTransX &&
-                    finalScale == anim.finalScale;
-
-        }
-    }
-
     private void animateFirstItem(final Drawable d, int duration, final boolean reverse,
             final Runnable onCompleteRunnable) {
-
         FolderPreviewItemAnim anim;
         if (!reverse) {
-            anim = new FolderPreviewItemAnim(mDrawingParams.get(0), -1, -1, 0, 2, duration,
+            anim = new FolderPreviewItemAnim(this, mDrawingParams.get(0), -1, -1, 0, 2, duration,
                     onCompleteRunnable);
         } else {
-            anim = new FolderPreviewItemAnim(mDrawingParams.get(0), 0, 2, -1, -1, duration,
+            anim = new FolderPreviewItemAnim(this, mDrawingParams.get(0), 0, 2, -1, -1, duration,
                     onCompleteRunnable);
         }
         anim.start();
@@ -740,7 +629,7 @@
                     mReferenceDrawable = p.drawable;
                 }
             } else {
-                FolderPreviewItemAnim anim = new FolderPreviewItemAnim(p, i, prevNumItems, i,
+                FolderPreviewItemAnim anim = new FolderPreviewItemAnim(this, p, i, prevNumItems, i,
                         nItemsInPreview, DROP_IN_ANIMATION_DURATION, null);
 
                 if (p.anim != null) {
@@ -901,7 +790,7 @@
         }
     }
 
-    public interface PreviewLayoutRule {
+    interface PreviewLayoutRule {
         PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
             PreviewItemDrawingParams params);
         void init(int availableSpace, float intrinsicIconSize, boolean rtl);
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index d0ac9f4..f62568f 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -701,10 +701,4 @@
     public int itemsPerPage() {
         return mMaxItemsPerPage;
     }
-
-    @Override
-    protected void getEdgeVerticalPosition(int[] pos) {
-        pos[0] = 0;
-        pos[1] = getViewportHeight();
-    }
 }
diff --git a/src/com/android/launcher3/folder/FolderPreviewItemAnim.java b/src/com/android/launcher3/folder/FolderPreviewItemAnim.java
new file mode 100644
index 0000000..0da7c5c
--- /dev/null
+++ b/src/com/android/launcher3/folder/FolderPreviewItemAnim.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2017 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.folder;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+
+import com.android.launcher3.LauncherAnimUtils;
+
+/**
+ * Animates a Folder preview item.
+ */
+class FolderPreviewItemAnim {
+    private ValueAnimator mValueAnimator;
+
+    float finalScale;
+    float finalTransX;
+    float finalTransY;
+
+    private PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0);
+
+    /**
+     * @param folderIcon The FolderIcon this preview will be drawn in.
+     * @param params layout params to animate
+     * @param index0 original index of the item to be animated
+     * @param items0 original number of items in the preview
+     * @param index1 new index of the item to be animated
+     * @param items1 new number of items in the preview
+     * @param duration duration in ms of the animation
+     * @param onCompleteRunnable runnable to execute upon animation completion
+     */
+    FolderPreviewItemAnim(final FolderIcon folderIcon, final PreviewItemDrawingParams params,
+            int index0, int items0, int index1, int items1, int duration,
+            final Runnable onCompleteRunnable) {
+        folderIcon.computePreviewItemDrawingParams(index1, items1, mTmpParams);
+
+        finalScale = mTmpParams.scale;
+        finalTransX = mTmpParams.transX;
+        finalTransY = mTmpParams.transY;
+
+        folderIcon.computePreviewItemDrawingParams(index0, items0, mTmpParams);
+
+        final float scale0 = mTmpParams.scale;
+        final float transX0 = mTmpParams.transX;
+        final float transY0 = mTmpParams.transY;
+
+        mValueAnimator = LauncherAnimUtils.ofFloat(0f, 1.0f);
+        mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
+            public void onAnimationUpdate(ValueAnimator animation) {
+                float progress = animation.getAnimatedFraction();
+
+                params.transX = transX0 + progress * (finalTransX - transX0);
+                params.transY = transY0 + progress * (finalTransY - transY0);
+                params.scale = scale0 + progress * (finalScale - scale0);
+                folderIcon.invalidate();
+            }
+        });
+        mValueAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (onCompleteRunnable != null) {
+                    onCompleteRunnable.run();
+                }
+                params.anim = null;
+            }
+        });
+        mValueAnimator.setDuration(duration);
+    }
+
+    public void start() {
+        mValueAnimator.start();
+    }
+
+    public void cancel() {
+        mValueAnimator.cancel();
+    }
+
+    public boolean hasEqualFinalState(FolderPreviewItemAnim anim) {
+        return finalTransY == anim.finalTransY && finalTransX == anim.finalTransX &&
+                finalScale == anim.finalScale;
+
+    }
+}
diff --git a/src/com/android/launcher3/folder/PreviewItemDrawingParams.java b/src/com/android/launcher3/folder/PreviewItemDrawingParams.java
new file mode 100644
index 0000000..607b7ca
--- /dev/null
+++ b/src/com/android/launcher3/folder/PreviewItemDrawingParams.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 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.folder;
+
+import android.graphics.drawable.Drawable;
+
+/**
+ * Manages the parameters used to draw a Folder preview item.
+ */
+class PreviewItemDrawingParams {
+    float transX;
+    float transY;
+    float scale;
+    float overlayAlpha;
+    FolderPreviewItemAnim anim;
+    public boolean hidden;
+    Drawable drawable;
+
+    PreviewItemDrawingParams(float transX, float transY, float scale, float overlayAlpha) {
+        this.transX = transX;
+        this.transY = transY;
+        this.scale = scale;
+        this.overlayAlpha = overlayAlpha;
+    }
+
+    public void update(float transX, float transY, float scale) {
+        // We ensure the update will not interfere with an animation on the layout params
+        // If the final values differ, we cancel the animation.
+        if (anim != null) {
+            if (anim.finalTransX == transX || anim.finalTransY == transY
+                    || anim.finalScale == scale) {
+                return;
+            }
+            anim.cancel();
+        }
+
+        this.transX = transX;
+        this.transY = transY;
+        this.scale = scale;
+    }
+}
diff --git a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
index 1ece278..138dc1c 100644
--- a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
+++ b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3.folder;
 
-import com.android.launcher3.folder.FolderIcon.PreviewItemDrawingParams;
-
 public class StackFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule {
 
     static final int MAX_NUM_ITEMS_IN_PREVIEW = 3;
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index 492d853..10e91c0 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -23,12 +23,11 @@
 import android.graphics.Region.Op;
 import android.graphics.drawable.Drawable;
 import android.view.View;
-import android.widget.TextView;
 
+import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppWidgetHostView;
 import com.android.launcher3.R;
-import com.android.launcher3.Workspace;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.FolderIcon;
 
@@ -57,8 +56,8 @@
         blurSizeOutline =
                 context.getResources().getDimensionPixelSize(R.dimen.blur_size_medium_outline);
 
-        if (mView instanceof TextView) {
-            Drawable d = Workspace.getTextViewIcon((TextView) mView);
+        if (mView instanceof BubbleTextView) {
+            Drawable d = ((BubbleTextView) mView).getIcon();
             Rect bounds = getDrawableBounds(d);
             previewPadding = blurSizeOutline - bounds.left - bounds.top;
         } else {
@@ -71,8 +70,8 @@
      */
     private void drawDragView(Canvas destCanvas) {
         destCanvas.save();
-        if (mView instanceof TextView) {
-            Drawable d = Workspace.getTextViewIcon((TextView) mView);
+        if (mView instanceof BubbleTextView) {
+            Drawable d = ((BubbleTextView) mView).getIcon();
             Rect bounds = getDrawableBounds(d);
             destCanvas.translate(blurSizeOutline / 2 - bounds.left,
                     blurSizeOutline / 2 - bounds.top);
@@ -112,8 +111,8 @@
         int width = mView.getWidth();
         int height = mView.getHeight();
 
-        if (mView instanceof TextView) {
-            Drawable d = Workspace.getTextViewIcon((TextView) mView);
+        if (mView instanceof BubbleTextView) {
+            Drawable d = ((BubbleTextView) mView).getIcon();
             Rect bounds = getDrawableBounds(d);
             width = bounds.width();
             height = bounds.height();
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index b27ccfd..68012c4 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -20,9 +20,9 @@
 import android.content.pm.LauncherActivityInfo;
 import android.os.Process;
 import android.os.UserHandle;
+import android.util.ArrayMap;
 import android.util.LongSparseArray;
 import android.util.Pair;
-
 import com.android.launcher3.AllAppsList;
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.FolderInfo;
@@ -39,9 +39,7 @@
 import com.android.launcher3.util.GridOccupancy;
 import com.android.launcher3.util.ManagedProfileHeuristic.UserFolderInfo;
 import com.android.launcher3.util.Provider;
-
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
 
 /**
@@ -68,7 +66,7 @@
 
         final ArrayList<ItemInfo> addedItemsFinal = new ArrayList<>();
         final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<>();
-        HashMap<UserHandle, UserFolderInfo> userFolderMap = new HashMap<>();
+        ArrayMap<UserHandle, UserFolderInfo> userFolderMap = new ArrayMap<>();
 
         // Get the list of workspace screens.  We need to append to this list and
         // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
@@ -146,8 +144,8 @@
             scheduleCallbackTask(new CallbackTask() {
                 @Override
                 public void execute(Callbacks callbacks) {
-                    final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
-                    final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
+                    final ArrayList<ItemInfo> addAnimated = new ArrayList<>();
+                    final ArrayList<ItemInfo> addNotAnimated = new ArrayList<>();
                     if (!addedItemsFinal.isEmpty()) {
                         ItemInfo info = addedItemsFinal.get(addedItemsFinal.size() - 1);
                         long lastScreenId = info.screenId;
diff --git a/src/com/android/launcher3/util/LauncherEdgeEffect.java b/src/com/android/launcher3/util/LauncherEdgeEffect.java
deleted file mode 100644
index 3e3b255..0000000
--- a/src/com/android/launcher3/util/LauncherEdgeEffect.java
+++ /dev/null
@@ -1,365 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.util;
-
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.view.animation.AnimationUtils;
-import android.view.animation.DecelerateInterpolator;
-import android.view.animation.Interpolator;
-
-/**
- * This class differs from the framework {@link android.widget.EdgeEffect}:
- *   1) It does not use PorterDuffXfermode
- *   2) The width to radius factor is smaller (0.5 instead of 0.75)
- */
-public class LauncherEdgeEffect {
-
-    // Time it will take the effect to fully recede in ms
-    private static final int RECEDE_TIME = 600;
-
-    // Time it will take before a pulled glow begins receding in ms
-    private static final int PULL_TIME = 167;
-
-    // Time it will take in ms for a pulled glow to decay to partial strength before release
-    private static final int PULL_DECAY_TIME = 2000;
-
-    private static final float MAX_ALPHA = 0.5f;
-
-    private static final float MAX_GLOW_SCALE = 2.f;
-
-    private static final float PULL_GLOW_BEGIN = 0.f;
-
-    // Minimum velocity that will be absorbed
-    private static final int MIN_VELOCITY = 100;
-    // Maximum velocity, clamps at this value
-    private static final int MAX_VELOCITY = 10000;
-
-    private static final float EPSILON = 0.001f;
-
-    private static final double ANGLE = Math.PI / 6;
-    private static final float SIN = (float) Math.sin(ANGLE);
-    private static final float COS = (float) Math.cos(ANGLE);
-
-    private float mGlowAlpha;
-    private float mGlowScaleY;
-
-    private float mGlowAlphaStart;
-    private float mGlowAlphaFinish;
-    private float mGlowScaleYStart;
-    private float mGlowScaleYFinish;
-
-    private long mStartTime;
-    private float mDuration;
-
-    private final Interpolator mInterpolator;
-
-    private static final int STATE_IDLE = 0;
-    private static final int STATE_PULL = 1;
-    private static final int STATE_ABSORB = 2;
-    private static final int STATE_RECEDE = 3;
-    private static final int STATE_PULL_DECAY = 4;
-
-    private static final float PULL_DISTANCE_ALPHA_GLOW_FACTOR = 0.8f;
-
-    private static final int VELOCITY_GLOW_FACTOR = 6;
-
-    private int mState = STATE_IDLE;
-
-    private float mPullDistance;
-
-    private final Rect mBounds = new Rect();
-    private final Paint mPaint = new Paint();
-    private float mRadius;
-    private float mBaseGlowScale;
-    private float mDisplacement = 0.5f;
-    private float mTargetDisplacement = 0.5f;
-
-    /**
-     * Construct a new EdgeEffect with a theme appropriate for the provided context.
-     */
-    public LauncherEdgeEffect() {
-        mPaint.setAntiAlias(true);
-        mPaint.setStyle(Paint.Style.FILL);
-        mInterpolator = new DecelerateInterpolator();
-    }
-
-    /**
-     * Set the size of this edge effect in pixels.
-     *
-     * @param width Effect width in pixels
-     * @param height Effect height in pixels
-     */
-    public void setSize(int width, int height) {
-        final float r = width * 0.5f / SIN;
-        final float y = COS * r;
-        final float h = r - y;
-        final float or = height * 0.75f / SIN;
-        final float oy = COS * or;
-        final float oh = or - oy;
-
-        mRadius = r;
-        mBaseGlowScale = h > 0 ? Math.min(oh / h, 1.f) : 1.f;
-
-        mBounds.set(mBounds.left, mBounds.top, width, (int) Math.min(height, h));
-    }
-
-    /**
-     * Reports if this EdgeEffect's animation is finished. If this method returns false
-     * after a call to {@link #draw(Canvas)} the host widget should schedule another
-     * drawing pass to continue the animation.
-     *
-     * @return true if animation is finished, false if drawing should continue on the next frame.
-     */
-    public boolean isFinished() {
-        return mState == STATE_IDLE;
-    }
-
-    /**
-     * Immediately finish the current animation.
-     * After this call {@link #isFinished()} will return true.
-     */
-    public void finish() {
-        mState = STATE_IDLE;
-    }
-
-    /**
-     * A view should call this when content is pulled away from an edge by the user.
-     * This will update the state of the current visual effect and its associated animation.
-     * The host view should always {@link android.view.View#invalidate()} after this
-     * and draw the results accordingly.
-     *
-     * <p>Views using EdgeEffect should favor {@link #onPull(float, float)} when the displacement
-     * of the pull point is known.</p>
-     *
-     * @param deltaDistance Change in distance since the last call. Values may be 0 (no change) to
-     *                      1.f (full length of the view) or negative values to express change
-     *                      back toward the edge reached to initiate the effect.
-     */
-    public void onPull(float deltaDistance) {
-        onPull(deltaDistance, 0.5f);
-    }
-
-    /**
-     * A view should call this when content is pulled away from an edge by the user.
-     * This will update the state of the current visual effect and its associated animation.
-     * The host view should always {@link android.view.View#invalidate()} after this
-     * and draw the results accordingly.
-     *
-     * @param deltaDistance Change in distance since the last call. Values may be 0 (no change) to
-     *                      1.f (full length of the view) or negative values to express change
-     *                      back toward the edge reached to initiate the effect.
-     * @param displacement The displacement from the starting side of the effect of the point
-     *                     initiating the pull. In the case of touch this is the finger position.
-     *                     Values may be from 0-1.
-     */
-    public void onPull(float deltaDistance, float displacement) {
-        final long now = AnimationUtils.currentAnimationTimeMillis();
-        mTargetDisplacement = displacement;
-        if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration) {
-            return;
-        }
-        if (mState != STATE_PULL) {
-            mGlowScaleY = Math.max(PULL_GLOW_BEGIN, mGlowScaleY);
-        }
-        mState = STATE_PULL;
-
-        mStartTime = now;
-        mDuration = PULL_TIME;
-
-        mPullDistance += deltaDistance;
-
-        final float absdd = Math.abs(deltaDistance);
-        mGlowAlpha = mGlowAlphaStart = Math.min(MAX_ALPHA,
-                mGlowAlpha + (absdd * PULL_DISTANCE_ALPHA_GLOW_FACTOR));
-
-        if (mPullDistance == 0) {
-            mGlowScaleY = mGlowScaleYStart = 0;
-        } else {
-            final float scale = (float) (Math.max(0, 1 - 1 /
-                    Math.sqrt(Math.abs(mPullDistance) * mBounds.height()) - 0.3d) / 0.7d);
-
-            mGlowScaleY = mGlowScaleYStart = scale;
-        }
-
-        mGlowAlphaFinish = mGlowAlpha;
-        mGlowScaleYFinish = mGlowScaleY;
-    }
-
-    /**
-     * Call when the object is released after being pulled.
-     * This will begin the "decay" phase of the effect. After calling this method
-     * the host view should {@link android.view.View#invalidate()} and thereby
-     * draw the results accordingly.
-     */
-    public void onRelease() {
-        mPullDistance = 0;
-
-        if (mState != STATE_PULL && mState != STATE_PULL_DECAY) {
-            return;
-        }
-
-        mState = STATE_RECEDE;
-        mGlowAlphaStart = mGlowAlpha;
-        mGlowScaleYStart = mGlowScaleY;
-
-        mGlowAlphaFinish = 0.f;
-        mGlowScaleYFinish = 0.f;
-
-        mStartTime = AnimationUtils.currentAnimationTimeMillis();
-        mDuration = RECEDE_TIME;
-    }
-
-    /**
-     * Call when the effect absorbs an impact at the given velocity.
-     * Used when a fling reaches the scroll boundary.
-     *
-     * <p>When using a {@link android.widget.Scroller} or {@link android.widget.OverScroller},
-     * the method <code>getCurrVelocity</code> will provide a reasonable approximation
-     * to use here.</p>
-     *
-     * @param velocity Velocity at impact in pixels per second.
-     */
-    public void onAbsorb(int velocity) {
-        mState = STATE_ABSORB;
-        velocity = Math.min(Math.max(MIN_VELOCITY, Math.abs(velocity)), MAX_VELOCITY);
-
-        mStartTime = AnimationUtils.currentAnimationTimeMillis();
-        mDuration = 0.15f + (velocity * 0.02f);
-
-        // The glow depends more on the velocity, and therefore starts out
-        // nearly invisible.
-        mGlowAlphaStart = 0.3f;
-        mGlowScaleYStart = Math.max(mGlowScaleY, 0.f);
-
-
-        // Growth for the size of the glow should be quadratic to properly
-        // respond
-        // to a user's scrolling speed. The faster the scrolling speed, the more
-        // intense the effect should be for both the size and the saturation.
-        mGlowScaleYFinish = Math.min(0.025f + (velocity * (velocity / 100) * 0.00015f) / 2, 1.f);
-        // Alpha should change for the glow as well as size.
-        mGlowAlphaFinish = Math.max(
-                mGlowAlphaStart, Math.min(velocity * VELOCITY_GLOW_FACTOR * .00001f, MAX_ALPHA));
-        mTargetDisplacement = 0.5f;
-    }
-
-    /**
-     * Set the color of this edge effect in argb.
-     *
-     * @param color Color in argb
-     */
-    public void setColor(int color) {
-        mPaint.setColor(color);
-    }
-
-    /**
-     * Return the color of this edge effect in argb.
-     * @return The color of this edge effect in argb
-     */
-    public int getColor() {
-        return mPaint.getColor();
-    }
-
-    /**
-     * Draw into the provided canvas. Assumes that the canvas has been rotated
-     * accordingly and the size has been set. The effect will be drawn the full
-     * width of X=0 to X=width, beginning from Y=0 and extending to some factor <
-     * 1.f of height.
-     *
-     * @param canvas Canvas to draw into
-     * @return true if drawing should continue beyond this frame to continue the
-     *         animation
-     */
-    public boolean draw(Canvas canvas) {
-        update();
-
-        final float centerX = mBounds.centerX();
-        final float centerY = mBounds.height() - mRadius;
-
-        canvas.scale(1.f, Math.min(mGlowScaleY, 1.f) * mBaseGlowScale, centerX, 0);
-
-        final float displacement = Math.max(0, Math.min(mDisplacement, 1.f)) - 0.5f;
-        float translateX = mBounds.width() * displacement / 2;
-        mPaint.setAlpha((int) (0xff * mGlowAlpha));
-        canvas.drawCircle(centerX + translateX, centerY, mRadius, mPaint);
-
-        boolean oneLastFrame = false;
-        if (mState == STATE_RECEDE && mGlowScaleY == 0) {
-            mState = STATE_IDLE;
-            oneLastFrame = true;
-        }
-
-        return mState != STATE_IDLE || oneLastFrame;
-    }
-
-    /**
-     * Return the maximum height that the edge effect will be drawn at given the original
-     * {@link #setSize(int, int) input size}.
-     * @return The maximum height of the edge effect
-     */
-    public int getMaxHeight() {
-        return (int) (mBounds.height() * MAX_GLOW_SCALE + 0.5f);
-    }
-
-    private void update() {
-        final long time = AnimationUtils.currentAnimationTimeMillis();
-        final float t = Math.min((time - mStartTime) / mDuration, 1.f);
-
-        final float interp = mInterpolator.getInterpolation(t);
-
-        mGlowAlpha = mGlowAlphaStart + (mGlowAlphaFinish - mGlowAlphaStart) * interp;
-        mGlowScaleY = mGlowScaleYStart + (mGlowScaleYFinish - mGlowScaleYStart) * interp;
-        mDisplacement = (mDisplacement + mTargetDisplacement) / 2;
-
-        if (t >= 1.f - EPSILON) {
-            switch (mState) {
-                case STATE_ABSORB:
-                    mState = STATE_RECEDE;
-                    mStartTime = AnimationUtils.currentAnimationTimeMillis();
-                    mDuration = RECEDE_TIME;
-
-                    mGlowAlphaStart = mGlowAlpha;
-                    mGlowScaleYStart = mGlowScaleY;
-
-                    // After absorb, the glow should fade to nothing.
-                    mGlowAlphaFinish = 0.f;
-                    mGlowScaleYFinish = 0.f;
-                    break;
-                case STATE_PULL:
-                    mState = STATE_PULL_DECAY;
-                    mStartTime = AnimationUtils.currentAnimationTimeMillis();
-                    mDuration = PULL_DECAY_TIME;
-
-                    mGlowAlphaStart = mGlowAlpha;
-                    mGlowScaleYStart = mGlowScaleY;
-
-                    // After pull, the glow should fade to nothing.
-                    mGlowAlphaFinish = 0.f;
-                    mGlowScaleYFinish = 0.f;
-                    break;
-                case STATE_PULL_DECAY:
-                    mState = STATE_RECEDE;
-                    break;
-                case STATE_RECEDE:
-                    mState = STATE_IDLE;
-                    break;
-            }
-        }
-    }
-}
diff --git a/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java b/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java
index 9c8457a..c0b5fe1 100644
--- a/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java
+++ b/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java
@@ -19,7 +19,9 @@
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Region;
+import android.support.v4.graphics.ColorUtils;
 import android.util.AttributeSet;
 
 import com.android.launcher3.BubbleTextView;
@@ -45,18 +47,20 @@
         mShadowInfo = new ShadowInfo(context, attrs, defStyle);
         setShadowLayer(mShadowInfo.ambientShadowBlur, 0, 0, mShadowInfo.ambientShadowColor);
     }
+
     @Override
     public void onDraw(Canvas canvas) {
         // If text is transparent, don't draw any shadow
-        if ((getCurrentTextColor() >> 24) == 0) {
+        int alpha = Color.alpha(getCurrentTextColor());
+        if (alpha == 0) {
             getPaint().clearShadowLayer();
             super.onDraw(canvas);
             return;
         }
 
         // We enhance the shadow by drawing the shadow twice
-        getPaint().setShadowLayer(
-                mShadowInfo.ambientShadowBlur, 0, 0, mShadowInfo.ambientShadowColor);
+        getPaint().setShadowLayer(mShadowInfo.ambientShadowBlur, 0, 0,
+                ColorUtils.setAlphaComponent(mShadowInfo.ambientShadowColor, alpha));
 
         drawWithoutBadge(canvas);
         canvas.save(Canvas.CLIP_SAVE_FLAG);
@@ -64,8 +68,8 @@
                 getScrollX() + getWidth(),
                 getScrollY() + getHeight(), Region.Op.INTERSECT);
 
-        getPaint().setShadowLayer(mShadowInfo.keyShadowBlur, 0.0f,
-                mShadowInfo.keyShadowOffset, mShadowInfo.keyShadowColor);
+        getPaint().setShadowLayer(mShadowInfo.keyShadowBlur, 0.0f, mShadowInfo.keyShadowOffset,
+                ColorUtils.setAlphaComponent(mShadowInfo.keyShadowColor, alpha));
         drawWithoutBadge(canvas);
         canvas.restore();
 
diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
new file mode 100644
index 0000000..7b5bcdb
--- /dev/null
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2017 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.views;
+
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.util.Property;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.widget.TextView;
+
+import com.android.launcher3.BaseRecyclerView;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.FastScrollThumbDrawable;
+import com.android.launcher3.util.Themes;
+
+/**
+ * The track and scrollbar that shows when you scroll the list.
+ */
+public class RecyclerViewFastScroller extends View {
+
+    private static final int SCROLL_DELTA_THRESHOLD_DP = 4;
+
+    private static final Property<RecyclerViewFastScroller, Integer> TRACK_WIDTH =
+            new Property<RecyclerViewFastScroller, Integer>(Integer.class, "width") {
+
+                @Override
+                public Integer get(RecyclerViewFastScroller scrollBar) {
+                    return scrollBar.mWidth;
+                }
+
+                @Override
+                public void set(RecyclerViewFastScroller scrollBar, Integer value) {
+                    scrollBar.setTrackWidth(value);
+                }
+            };
+
+    private final static int MAX_TRACK_ALPHA = 30;
+    private final static int SCROLL_BAR_VIS_DURATION = 150;
+    private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 0.75f;
+
+    private final int mMinWidth;
+    private final int mMaxWidth;
+    private final int mThumbPadding;
+
+    /** Keeps the last known scrolling delta/velocity along y-axis. */
+    private int mDy = 0;
+    private final float mDeltaThreshold;
+
+    private final ViewConfiguration mConfig;
+
+    // Current width of the track
+    private int mWidth;
+    private ObjectAnimator mWidthAnimator;
+
+    private final Paint mThumbPaint;
+    protected final int mThumbHeight;
+
+    private final Paint mTrackPaint;
+
+    private float mLastTouchY;
+    private boolean mIsDragging;
+    private boolean mIsThumbDetached;
+    private final boolean mCanThumbDetach;
+    private boolean mIgnoreDragGesture;
+
+    // This is the offset from the top of the scrollbar when the user first starts touching.  To
+    // prevent jumping, this offset is applied as the user scrolls.
+    protected int mTouchOffsetY;
+    protected int mThumbOffsetY;
+
+    // Fast scroller popup
+    private TextView mPopupView;
+    private boolean mPopupVisible;
+    private String mPopupSectionName;
+
+    protected BaseRecyclerView mRv;
+
+    private int mDownX;
+    private int mDownY;
+    private int mLastY;
+
+    public RecyclerViewFastScroller(Context context) {
+        this(context, null);
+    }
+
+    public RecyclerViewFastScroller(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public RecyclerViewFastScroller(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+
+        mTrackPaint = new Paint();
+        mTrackPaint.setColor(Themes.getAttrColor(context, android.R.attr.textColorPrimary));
+        mTrackPaint.setAlpha(MAX_TRACK_ALPHA);
+
+        mThumbPaint = new Paint();
+        mThumbPaint.setAntiAlias(true);
+        mThumbPaint.setColor(Themes.getColorAccent(context));
+        mThumbPaint.setStyle(Paint.Style.FILL);
+
+        Resources res = getResources();
+        mWidth = mMinWidth = res.getDimensionPixelSize(R.dimen.fastscroll_track_min_width);
+        mMaxWidth = res.getDimensionPixelSize(R.dimen.fastscroll_track_max_width);
+
+        mThumbPadding = res.getDimensionPixelSize(R.dimen.fastscroll_thumb_padding);
+        mThumbHeight = res.getDimensionPixelSize(R.dimen.fastscroll_thumb_height);
+
+        mConfig = ViewConfiguration.get(context);
+        mDeltaThreshold = res.getDisplayMetrics().density * SCROLL_DELTA_THRESHOLD_DP;
+
+        TypedArray ta =
+                context.obtainStyledAttributes(attrs, R.styleable.RecyclerViewFastScroller, defStyleAttr, 0);
+        mCanThumbDetach = ta.getBoolean(R.styleable.RecyclerViewFastScroller_canThumbDetach, false);
+        ta.recycle();
+    }
+
+    public void setRecyclerView(BaseRecyclerView rv, TextView popupView) {
+        mRv = rv;
+        mRv.addOnScrollListener(new RecyclerView.OnScrollListener() {
+            @Override
+            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+                mDy = dy;
+
+                // TODO(winsonc): If we want to animate the section heads while scrolling, we can
+                //                initiate that here if the recycler view scroll state is not
+                //                RecyclerView.SCROLL_STATE_IDLE.
+
+                mRv.onUpdateScrollbar(dy);
+            }
+        });
+
+        mPopupView = popupView;
+        mPopupView.setBackground(
+                new FastScrollThumbDrawable(mThumbPaint, Utilities.isRtl(getResources())));
+    }
+
+    public void reattachThumbToScroll() {
+        mIsThumbDetached = false;
+    }
+
+    public void setThumbOffsetY(int y) {
+        if (mThumbOffsetY == y) {
+            return;
+        }
+        mThumbOffsetY = y;
+        invalidate();
+    }
+
+    public int getThumbOffsetY() {
+        return mThumbOffsetY;
+    }
+
+    private void setTrackWidth(int width) {
+        if (mWidth == width) {
+            return;
+        }
+        mWidth = width;
+        invalidate();
+    }
+
+    public int getThumbHeight() {
+        return mThumbHeight;
+    }
+
+    public boolean isDraggingThumb() {
+        return mIsDragging;
+    }
+
+    public boolean isThumbDetached() {
+        return mIsThumbDetached;
+    }
+
+    /**
+     * Handles the touch event and determines whether to show the fast scroller (or updates it if
+     * it is already showing).
+     */
+    public boolean handleTouchEvent(MotionEvent ev) {
+        int x = (int) ev.getX();
+        int y = (int) ev.getY();
+        switch (ev.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                // Keep track of the down positions
+                mDownX = x;
+                mDownY = mLastY = y;
+
+                if ((Math.abs(mDy) < mDeltaThreshold &&
+                        mRv.getScrollState() != RecyclerView.SCROLL_STATE_IDLE)) {
+                    // now the touch events are being passed to the {@link WidgetCell} until the
+                    // touch sequence goes over the touch slop.
+                    mRv.stopScroll();
+                }
+                if (isNearThumb(x, y)) {
+                    mTouchOffsetY = mDownY - mThumbOffsetY;
+                } else if (FeatureFlags.LAUNCHER3_DIRECT_SCROLL
+                        && mRv.supportsFastScrolling()
+                        && isNearScrollBar(mDownX)) {
+                    calcTouchOffsetAndPrepToFastScroll(mDownY, mLastY);
+                    updateFastScrollSectionNameAndThumbOffset(mLastY, y);
+                }
+                break;
+            case MotionEvent.ACTION_MOVE:
+                mLastY = y;
+
+                // Check if we should start scrolling, but ignore this fastscroll gesture if we have
+                // exceeded some fixed movement
+                mIgnoreDragGesture |= Math.abs(y - mDownY) > mConfig.getScaledPagingTouchSlop();
+                if (!mIsDragging && !mIgnoreDragGesture && mRv.supportsFastScrolling() &&
+                        isNearThumb(mDownX, mLastY) &&
+                        Math.abs(y - mDownY) > mConfig.getScaledTouchSlop()) {
+                    calcTouchOffsetAndPrepToFastScroll(mDownY, mLastY);
+                }
+                if (mIsDragging) {
+                    updateFastScrollSectionNameAndThumbOffset(mLastY, y);
+                }
+                break;
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                mRv.onFastScrollCompleted();
+                mTouchOffsetY = 0;
+                mLastTouchY = 0;
+                mIgnoreDragGesture = false;
+                if (mIsDragging) {
+                    mIsDragging = false;
+                    animatePopupVisibility(false);
+                    showActiveScrollbar(false);
+                }
+                break;
+        }
+        return mIsDragging;
+    }
+
+    private void calcTouchOffsetAndPrepToFastScroll(int downY, int lastY) {
+        mRv.getParent().requestDisallowInterceptTouchEvent(true);
+        mIsDragging = true;
+        if (mCanThumbDetach) {
+            mIsThumbDetached = true;
+        }
+        mTouchOffsetY += (lastY - downY);
+        animatePopupVisibility(true);
+        showActiveScrollbar(true);
+    }
+
+    private void updateFastScrollSectionNameAndThumbOffset(int lastY, int y) {
+        // Update the fastscroller section name at this touch position
+        int bottom = mRv.getScrollbarTrackHeight() - mThumbHeight;
+        float boundedY = (float) Math.max(0, Math.min(bottom, y - mTouchOffsetY));
+        String sectionName = mRv.scrollToPositionAtProgress(boundedY / bottom);
+        if (!sectionName.equals(mPopupSectionName)) {
+            mPopupSectionName = sectionName;
+            mPopupView.setText(sectionName);
+        }
+        animatePopupVisibility(!sectionName.isEmpty());
+        updatePopupY(lastY);
+        mLastTouchY = boundedY;
+        setThumbOffsetY((int) mLastTouchY);
+    }
+
+    public void onDraw(Canvas canvas) {
+        if (mThumbOffsetY < 0) {
+            return;
+        }
+        int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
+        canvas.translate(getWidth() / 2, mRv.getPaddingTop());
+        // Draw the track
+        float halfW = mWidth / 2;
+        canvas.drawRoundRect(-halfW, 0, halfW, mRv.getScrollbarTrackHeight(),
+                mWidth, mWidth, mTrackPaint);
+
+        canvas.translate(0, mThumbOffsetY);
+        halfW += mThumbPadding;
+        float r = mWidth + mThumbPadding + mThumbPadding;
+        canvas.drawRoundRect(-halfW, 0, halfW, mThumbHeight, r, r, mThumbPaint);
+        canvas.restoreToCount(saveCount);
+    }
+
+
+    /**
+     * Animates the width of the scrollbar.
+     */
+    private void showActiveScrollbar(boolean isScrolling) {
+        if (mWidthAnimator != null) {
+            mWidthAnimator.cancel();
+        }
+
+        mWidthAnimator = ObjectAnimator.ofInt(this, TRACK_WIDTH,
+                isScrolling ? mMaxWidth : mMinWidth);
+        mWidthAnimator.setDuration(SCROLL_BAR_VIS_DURATION);
+        mWidthAnimator.start();
+    }
+
+    /**
+     * Returns whether the specified point is inside the thumb bounds.
+     */
+    private boolean isNearThumb(int x, int y) {
+        int offset = y - mRv.getPaddingTop() - mThumbOffsetY;
+
+        return x >= 0 && x < getWidth() && offset >= 0 && offset <= mThumbHeight;
+    }
+
+    /**
+     * Returns true if AllAppsTransitionController can handle vertical motion
+     * beginning at this point.
+     */
+    public boolean shouldBlockIntercept(int x, int y) {
+        return isNearThumb(x, y);
+    }
+
+    /**
+     * Returns whether the specified x position is near the scroll bar.
+     */
+    public boolean isNearScrollBar(int x) {
+        return x >= (getWidth() - mMaxWidth) / 2 && x <= (getWidth() + mMaxWidth) / 2;
+    }
+
+    private void animatePopupVisibility(boolean visible) {
+        if (mPopupVisible != visible) {
+            mPopupVisible = visible;
+            mPopupView.animate().cancel();
+            mPopupView.animate().alpha(visible ? 1f : 0f).setDuration(visible ? 200 : 150).start();
+        }
+    }
+
+    private void updatePopupY(int lastTouchY) {
+        int height = mPopupView.getHeight();
+        float top = lastTouchY - (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * height)
+                + mRv.getPaddingTop();
+        top = Utilities.boundToRange(top,
+                mMaxWidth, mRv.getScrollbarTrackHeight() - mMaxWidth - height);
+        mPopupView.setTranslationY(top);
+    }
+}
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index d87e7fb..fff3472 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -22,7 +22,6 @@
 import android.content.Context;
 import android.graphics.Rect;
 import android.util.AttributeSet;
-import android.view.ContextThemeWrapper;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
@@ -77,7 +76,7 @@
     }
 
     public WidgetsBottomSheet(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(new ContextThemeWrapper(context, R.style.WidgetContainerTheme), attrs, defStyleAttr);
+        super(context, attrs, defStyleAttr);
         setWillNotDraw(false);
         mLauncher = Launcher.getLauncher(context);
         mOpenCloseAnimator = LauncherAnimUtils.ofPropertyValuesHolder(this);